crewly 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +96 -0
- package/config/auto-assign/default-config.yaml +74 -0
- package/config/constants.d.ts.map +1 -0
- package/config/constants.test.ts +469 -0
- package/config/constants.ts +827 -0
- package/config/continuation/prompts/continue-work.md +60 -0
- package/config/continuation/prompts/max-iterations.md +41 -0
- package/config/continuation/prompts/quality-gates-failed.md +37 -0
- package/config/continuation/prompts/retry-error.md +59 -0
- package/config/continuation/prompts/task-assignment.md +60 -0
- package/config/index.d.ts.map +1 -0
- package/config/index.ts +87 -0
- package/config/orchestrator_tasks/prompts/assign-task-orchestrator-prompt-template.md +70 -0
- package/config/orchestrator_tasks/prompts/checkin-orchestrator-prompt-template.md +138 -0
- package/config/orchestrator_tasks/prompts/orchestrator-prompt.md +68 -0
- package/config/orchestrator_tasks/prompts/project-start-orchestrator-prompt-template.md +104 -0
- package/config/quality-gates/default-gates.test.ts +246 -0
- package/config/quality-gates/default-gates.ts +191 -0
- package/config/quality-gates/index.ts +21 -0
- package/config/roles/_common/memory-instructions.md +56 -0
- package/config/roles/architect/prompt.md +87 -0
- package/config/roles/architect/role.json +13 -0
- package/config/roles/backend-developer/prompt.md +86 -0
- package/config/roles/backend-developer/role.json +13 -0
- package/config/roles/designer/prompt.md +86 -0
- package/config/roles/designer/role.json +13 -0
- package/config/roles/developer/prompt.md +94 -0
- package/config/roles/developer/role.json +13 -0
- package/config/roles/frontend-developer/prompt.md +86 -0
- package/config/roles/frontend-developer/role.json +13 -0
- package/config/roles/fullstack-dev/prompt.md +86 -0
- package/config/roles/fullstack-dev/role.json +13 -0
- package/config/roles/generalist/prompt.md +87 -0
- package/config/roles/generalist/role.json +13 -0
- package/config/roles/orchestrator/prompt.md +865 -0
- package/config/roles/orchestrator/role.json +13 -0
- package/config/roles/product-manager/prompt.md +86 -0
- package/config/roles/product-manager/role.json +13 -0
- package/config/roles/qa/prompt.md +86 -0
- package/config/roles/qa/role.json +13 -0
- package/config/roles/qa-engineer/prompt.md +86 -0
- package/config/roles/qa-engineer/role.json +13 -0
- package/config/roles/sales/prompt.md +86 -0
- package/config/roles/sales/role.json +13 -0
- package/config/roles/support/prompt.md +86 -0
- package/config/roles/support/role.json +13 -0
- package/config/roles/tpm/prompt.md +87 -0
- package/config/roles/tpm/role.json +13 -0
- package/config/runtime_scripts/initialize_claude.sh +14 -0
- package/config/runtime_scripts/initialize_codex.sh +14 -0
- package/config/runtime_scripts/runtime-config.json +26 -0
- package/config/skills/_common/lib.sh +64 -0
- package/config/skills/agent/_common/lib.sh +4 -0
- package/config/skills/agent/accept-task/execute.sh +21 -0
- package/config/skills/agent/accept-task/instructions.md +20 -0
- package/config/skills/agent/accept-task/skill.json +20 -0
- package/config/skills/agent/block-task/execute.sh +26 -0
- package/config/skills/agent/block-task/instructions.md +22 -0
- package/config/skills/agent/block-task/skill.json +20 -0
- package/config/skills/agent/check-quality-gates/execute.sh +20 -0
- package/config/skills/agent/check-quality-gates/instructions.md +23 -0
- package/config/skills/agent/check-quality-gates/skill.json +20 -0
- package/config/skills/agent/complete-task/execute.sh +26 -0
- package/config/skills/agent/complete-task/instructions.md +22 -0
- package/config/skills/agent/complete-task/skill.json +20 -0
- package/config/skills/agent/get-my-context/execute.sh +23 -0
- package/config/skills/agent/get-my-context/instructions.md +21 -0
- package/config/skills/agent/get-my-context/skill.json +20 -0
- package/config/skills/agent/get-sops/execute.sh +24 -0
- package/config/skills/agent/get-sops/instructions.md +21 -0
- package/config/skills/agent/get-sops/skill.json +20 -0
- package/config/skills/agent/get-team-status/execute.sh +8 -0
- package/config/skills/agent/get-team-status/instructions.md +17 -0
- package/config/skills/agent/get-team-status/skill.json +20 -0
- package/config/skills/agent/manage-knowledge/execute.sh +60 -0
- package/config/skills/agent/manage-knowledge/instructions.md +46 -0
- package/config/skills/agent/nano-banana-image/.env +2 -0
- package/config/skills/agent/nano-banana-image/.env.example +6 -0
- package/config/skills/agent/nano-banana-image/generate.sh +73 -0
- package/config/skills/agent/nano-banana-image/instructions.md +50 -0
- package/config/skills/agent/nano-banana-image/skill.json +39 -0
- package/config/skills/agent/query-knowledge/execute.sh +30 -0
- package/config/skills/agent/query-knowledge/instructions.md +47 -0
- package/config/skills/agent/query-knowledge/skill.json +20 -0
- package/config/skills/agent/read-task/execute.sh +15 -0
- package/config/skills/agent/read-task/instructions.md +19 -0
- package/config/skills/agent/read-task/skill.json +20 -0
- package/config/skills/agent/recall/execute.sh +24 -0
- package/config/skills/agent/recall/instructions.md +23 -0
- package/config/skills/agent/recall/skill.json +20 -0
- package/config/skills/agent/record-learning/execute.sh +29 -0
- package/config/skills/agent/record-learning/instructions.md +24 -0
- package/config/skills/agent/record-learning/skill.json +20 -0
- package/config/skills/agent/register-self/execute.sh +28 -0
- package/config/skills/agent/register-self/instructions.md +18 -0
- package/config/skills/agent/register-self/skill.json +20 -0
- package/config/skills/agent/remember/execute.sh +29 -0
- package/config/skills/agent/remember/instructions.md +24 -0
- package/config/skills/agent/remember/skill.json +20 -0
- package/config/skills/agent/report-progress/execute.sh +28 -0
- package/config/skills/agent/report-progress/instructions.md +25 -0
- package/config/skills/agent/report-progress/skill.json +20 -0
- package/config/skills/agent/report-status/execute.sh +35 -0
- package/config/skills/agent/report-status/instructions.md +36 -0
- package/config/skills/agent/report-status/skill.json +20 -0
- package/config/skills/agent/send-chat-response/execute.sh +26 -0
- package/config/skills/agent/send-chat-response/instructions.md +22 -0
- package/config/skills/agent/send-chat-response/skill.json +20 -0
- package/config/skills/agent/send-message/execute.sh +17 -0
- package/config/skills/agent/send-message/instructions.md +20 -0
- package/config/skills/agent/send-message/skill.json +20 -0
- package/config/skills/agent/send-pdf-to-slack/execute.sh +182 -0
- package/config/skills/agent/send-pdf-to-slack/instructions.md +49 -0
- package/config/skills/agent/send-pdf-to-slack/skill.json +20 -0
- package/config/skills/chrome-browser/instructions.md +42 -0
- package/config/skills/chrome-browser/skill.json +39 -0
- package/config/skills/nano-banana-image/.env +2 -0
- package/config/skills/nano-banana-image/.env.example +6 -0
- package/config/skills/nano-banana-image/generate.sh +73 -0
- package/config/skills/nano-banana-image/instructions.md +50 -0
- package/config/skills/nano-banana-image/skill.json +39 -0
- package/config/skills/orchestrator/_common/lib.sh +4 -0
- package/config/skills/orchestrator/assign-task/execute.sh +11 -0
- package/config/skills/orchestrator/assign-task/instructions.md +17 -0
- package/config/skills/orchestrator/assign-task/skill.json +20 -0
- package/config/skills/orchestrator/assign-team-to-project/execute.sh +14 -0
- package/config/skills/orchestrator/assign-team-to-project/instructions.md +20 -0
- package/config/skills/orchestrator/assign-team-to-project/skill.json +20 -0
- package/config/skills/orchestrator/broadcast/execute.sh +35 -0
- package/config/skills/orchestrator/broadcast/instructions.md +19 -0
- package/config/skills/orchestrator/broadcast/skill.json +20 -0
- package/config/skills/orchestrator/cancel-schedule/execute.sh +13 -0
- package/config/skills/orchestrator/cancel-schedule/instructions.md +19 -0
- package/config/skills/orchestrator/cancel-schedule/skill.json +20 -0
- package/config/skills/orchestrator/complete-task/execute.sh +10 -0
- package/config/skills/orchestrator/complete-task/instructions.md +17 -0
- package/config/skills/orchestrator/complete-task/skill.json +20 -0
- package/config/skills/orchestrator/create-project/execute.sh +14 -0
- package/config/skills/orchestrator/create-project/instructions.md +21 -0
- package/config/skills/orchestrator/create-project/skill.json +20 -0
- package/config/skills/orchestrator/create-team/execute.sh +14 -0
- package/config/skills/orchestrator/create-team/instructions.md +21 -0
- package/config/skills/orchestrator/create-team/skill.json +20 -0
- package/config/skills/orchestrator/delegate-task/execute.sh +37 -0
- package/config/skills/orchestrator/delegate-task/instructions.md +23 -0
- package/config/skills/orchestrator/delegate-task/skill.json +20 -0
- package/config/skills/orchestrator/get-agent-logs/execute.sh +14 -0
- package/config/skills/orchestrator/get-agent-logs/instructions.md +20 -0
- package/config/skills/orchestrator/get-agent-logs/skill.json +20 -0
- package/config/skills/orchestrator/get-agent-status/execute.sh +22 -0
- package/config/skills/orchestrator/get-agent-status/instructions.md +19 -0
- package/config/skills/orchestrator/get-agent-status/skill.json +20 -0
- package/config/skills/orchestrator/get-project-overview/execute.sh +7 -0
- package/config/skills/orchestrator/get-project-overview/instructions.md +17 -0
- package/config/skills/orchestrator/get-project-overview/skill.json +20 -0
- package/config/skills/orchestrator/get-tasks/execute.sh +27 -0
- package/config/skills/orchestrator/get-tasks/instructions.md +17 -0
- package/config/skills/orchestrator/get-tasks/skill.json +20 -0
- package/config/skills/orchestrator/get-team-status/execute.sh +7 -0
- package/config/skills/orchestrator/get-team-status/instructions.md +17 -0
- package/config/skills/orchestrator/get-team-status/skill.json +20 -0
- package/config/skills/orchestrator/heartbeat/execute.sh +37 -0
- package/config/skills/orchestrator/heartbeat/instructions.md +24 -0
- package/config/skills/orchestrator/heartbeat/skill.json +20 -0
- package/config/skills/orchestrator/list-subscriptions/execute.sh +7 -0
- package/config/skills/orchestrator/list-subscriptions/instructions.md +17 -0
- package/config/skills/orchestrator/list-subscriptions/skill.json +20 -0
- package/config/skills/orchestrator/query-knowledge/execute.sh +30 -0
- package/config/skills/orchestrator/query-knowledge/instructions.md +47 -0
- package/config/skills/orchestrator/query-knowledge/skill.json +20 -0
- package/config/skills/orchestrator/recall/execute.sh +13 -0
- package/config/skills/orchestrator/recall/instructions.md +23 -0
- package/config/skills/orchestrator/recall/skill.json +20 -0
- package/config/skills/orchestrator/record-failure/execute.sh +13 -0
- package/config/skills/orchestrator/record-failure/instructions.md +22 -0
- package/config/skills/orchestrator/record-failure/skill.json +20 -0
- package/config/skills/orchestrator/record-learning/execute.sh +13 -0
- package/config/skills/orchestrator/record-learning/instructions.md +23 -0
- package/config/skills/orchestrator/record-learning/skill.json +20 -0
- package/config/skills/orchestrator/record-success/execute.sh +13 -0
- package/config/skills/orchestrator/record-success/instructions.md +22 -0
- package/config/skills/orchestrator/record-success/skill.json +20 -0
- package/config/skills/orchestrator/register-self/execute.sh +24 -0
- package/config/skills/orchestrator/register-self/instructions.md +21 -0
- package/config/skills/orchestrator/register-self/skill.json +20 -0
- package/config/skills/orchestrator/remember/execute.sh +15 -0
- package/config/skills/orchestrator/remember/instructions.md +24 -0
- package/config/skills/orchestrator/remember/skill.json +20 -0
- package/config/skills/orchestrator/reply-slack/execute.sh +174 -0
- package/config/skills/orchestrator/reply-slack/instructions.md +52 -0
- package/config/skills/orchestrator/reply-slack/skill.json +20 -0
- package/config/skills/orchestrator/restart-crewly/execute.sh +7 -0
- package/config/skills/orchestrator/restart-crewly/instructions.md +23 -0
- package/config/skills/orchestrator/restart-crewly/skill.json +20 -0
- package/config/skills/orchestrator/resume-session/execute.sh +22 -0
- package/config/skills/orchestrator/resume-session/instructions.md +31 -0
- package/config/skills/orchestrator/resume-session/skill.json +20 -0
- package/config/skills/orchestrator/schedule-check/execute.sh +35 -0
- package/config/skills/orchestrator/schedule-check/instructions.md +35 -0
- package/config/skills/orchestrator/schedule-check/skill.json +20 -0
- package/config/skills/orchestrator/send-key/execute.sh +17 -0
- package/config/skills/orchestrator/send-message/execute.sh +17 -0
- package/config/skills/orchestrator/send-message/instructions.md +20 -0
- package/config/skills/orchestrator/send-message/skill.json +20 -0
- package/config/skills/orchestrator/send-pdf-to-slack/execute.sh +182 -0
- package/config/skills/orchestrator/send-pdf-to-slack/instructions.md +49 -0
- package/config/skills/orchestrator/send-pdf-to-slack/skill.json +20 -0
- package/config/skills/orchestrator/set-goal/execute.sh +13 -0
- package/config/skills/orchestrator/set-goal/instructions.md +21 -0
- package/config/skills/orchestrator/set-goal/skill.json +20 -0
- package/config/skills/orchestrator/start-agent/execute.sh +15 -0
- package/config/skills/orchestrator/start-agent/instructions.md +20 -0
- package/config/skills/orchestrator/start-agent/skill.json +20 -0
- package/config/skills/orchestrator/start-team/execute.sh +14 -0
- package/config/skills/orchestrator/start-team/instructions.md +20 -0
- package/config/skills/orchestrator/start-team/skill.json +20 -0
- package/config/skills/orchestrator/stop-agent/execute.sh +15 -0
- package/config/skills/orchestrator/stop-agent/instructions.md +20 -0
- package/config/skills/orchestrator/stop-agent/skill.json +20 -0
- package/config/skills/orchestrator/stop-team/execute.sh +13 -0
- package/config/skills/orchestrator/stop-team/instructions.md +19 -0
- package/config/skills/orchestrator/stop-team/skill.json +20 -0
- package/config/skills/orchestrator/subscribe-event/execute.sh +19 -0
- package/config/skills/orchestrator/subscribe-event/instructions.md +21 -0
- package/config/skills/orchestrator/subscribe-event/skill.json +20 -0
- package/config/skills/orchestrator/terminate-agent/execute.sh +13 -0
- package/config/skills/orchestrator/terminate-agent/instructions.md +19 -0
- package/config/skills/orchestrator/terminate-agent/skill.json +20 -0
- package/config/skills/orchestrator/unsubscribe-event/execute.sh +13 -0
- package/config/skills/orchestrator/unsubscribe-event/instructions.md +19 -0
- package/config/skills/orchestrator/unsubscribe-event/skill.json +20 -0
- package/config/skills/orchestrator/update-focus/execute.sh +13 -0
- package/config/skills/orchestrator/update-focus/instructions.md +21 -0
- package/config/skills/orchestrator/update-focus/skill.json +20 -0
- package/config/skills/playwright-chrome-browser/instructions.md +95 -0
- package/config/skills/playwright-chrome-browser/skill.json +44 -0
- package/config/sops/common/blocker-handling.md +55 -0
- package/config/sops/common/communication-protocol.md +52 -0
- package/config/sops/developer/coding-standards.md +54 -0
- package/config/sops/developer/git-workflow.md +58 -0
- package/config/sops/developer/testing-requirements.md +62 -0
- package/config/sops/pm/progress-tracking.md +57 -0
- package/config/sops/pm/task-decomposition.md +59 -0
- package/config/sops/qa/testing-procedures.md +60 -0
- package/config/task_assignment/prompts/target-agent-assignment-prompt.md +119 -0
- package/config/task_starters/build_e2e_test_plan_prompt.json +51 -0
- package/config/task_starters/build_spec_prompt.json +79 -0
- package/config/task_starters/build_tasks_prompt.json +55 -0
- package/config/task_starters/prompts/build_spec_step1_project_requirements.md +120 -0
- package/config/task_starters/prompts/build_spec_step2_technical_design.md +147 -0
- package/config/task_starters/prompts/build_spec_step3_integration_testing.md +196 -0
- package/config/task_starters/prompts/build_tasks_step1_milestone_structure.md +274 -0
- package/config/task_starters/prompts/e2e_test_plan_step1_build_tests.md +391 -0
- package/config/task_starters/prompts/e2e_test_plan_step2_run_tests.md +240 -0
- package/dist/backend/backend/src/constants.d.ts +669 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -0
- package/dist/backend/backend/src/constants.js +482 -0
- package/dist/backend/backend/src/constants.js.map +1 -0
- package/dist/backend/backend/src/controllers/api.controller.d.ts +115 -0
- package/dist/backend/backend/src/controllers/api.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/api.controller.js +427 -0
- package/dist/backend/backend/src/controllers/api.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts +160 -0
- package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/chat/chat.controller.js +555 -0
- package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/chat/chat.routes.d.ts +18 -0
- package/dist/backend/backend/src/controllers/chat/chat.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/chat/chat.routes.js +39 -0
- package/dist/backend/backend/src/controllers/chat/chat.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/chat/index.d.ts +10 -0
- package/dist/backend/backend/src/controllers/chat/index.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/chat/index.js +10 -0
- package/dist/backend/backend/src/controllers/chat/index.js.map +1 -0
- package/dist/backend/backend/src/controllers/event-bus/event-bus.controller.d.ts +54 -0
- package/dist/backend/backend/src/controllers/event-bus/event-bus.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/event-bus/event-bus.controller.js +123 -0
- package/dist/backend/backend/src/controllers/event-bus/event-bus.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/event-bus/event-bus.routes.d.ts +15 -0
- package/dist/backend/backend/src/controllers/event-bus/event-bus.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/event-bus/event-bus.routes.js +27 -0
- package/dist/backend/backend/src/controllers/event-bus/event-bus.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/index.d.ts +10 -0
- package/dist/backend/backend/src/controllers/index.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/index.js +37 -0
- package/dist/backend/backend/src/controllers/index.js.map +1 -0
- package/dist/backend/backend/src/controllers/knowledge/index.d.ts +7 -0
- package/dist/backend/backend/src/controllers/knowledge/index.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/knowledge/index.js +7 -0
- package/dist/backend/backend/src/controllers/knowledge/index.js.map +1 -0
- package/dist/backend/backend/src/controllers/knowledge/knowledge.controller.d.ts +71 -0
- package/dist/backend/backend/src/controllers/knowledge/knowledge.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/knowledge/knowledge.controller.js +237 -0
- package/dist/backend/backend/src/controllers/knowledge/knowledge.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/knowledge/knowledge.routes.d.ts +24 -0
- package/dist/backend/backend/src/controllers/knowledge/knowledge.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/knowledge/knowledge.routes.js +34 -0
- package/dist/backend/backend/src/controllers/knowledge/knowledge.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/marketplace/index.d.ts +11 -0
- package/dist/backend/backend/src/controllers/marketplace/index.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/marketplace/index.js +11 -0
- package/dist/backend/backend/src/controllers/marketplace/index.js.map +1 -0
- package/dist/backend/backend/src/controllers/marketplace/marketplace.controller.d.ts +135 -0
- package/dist/backend/backend/src/controllers/marketplace/marketplace.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/marketplace/marketplace.controller.js +228 -0
- package/dist/backend/backend/src/controllers/marketplace/marketplace.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/marketplace/marketplace.routes.d.ts +30 -0
- package/dist/backend/backend/src/controllers/marketplace/marketplace.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/marketplace/marketplace.routes.js +44 -0
- package/dist/backend/backend/src/controllers/marketplace/marketplace.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/memory/index.d.ts +11 -0
- package/dist/backend/backend/src/controllers/memory/index.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/memory/index.js +11 -0
- package/dist/backend/backend/src/controllers/memory/index.js.map +1 -0
- package/dist/backend/backend/src/controllers/memory/memory.controller.d.ts +183 -0
- package/dist/backend/backend/src/controllers/memory/memory.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/memory/memory.controller.js +505 -0
- package/dist/backend/backend/src/controllers/memory/memory.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/memory/memory.routes.d.ts +30 -0
- package/dist/backend/backend/src/controllers/memory/memory.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/memory/memory.routes.js +46 -0
- package/dist/backend/backend/src/controllers/memory/memory.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/messaging/delivery-logs.controller.d.ts +5 -0
- package/dist/backend/backend/src/controllers/messaging/delivery-logs.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/messaging/delivery-logs.controller.js +21 -0
- package/dist/backend/backend/src/controllers/messaging/delivery-logs.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/messaging/messaging.controller.d.ts +72 -0
- package/dist/backend/backend/src/controllers/messaging/messaging.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/messaging/messaging.controller.js +164 -0
- package/dist/backend/backend/src/controllers/messaging/messaging.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/messaging/messaging.routes.d.ts +15 -0
- package/dist/backend/backend/src/controllers/messaging/messaging.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/messaging/messaging.routes.js +31 -0
- package/dist/backend/backend/src/controllers/messaging/messaging.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/messaging/scheduled-messages.controller.d.ts +10 -0
- package/dist/backend/backend/src/controllers/messaging/scheduled-messages.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/messaging/scheduled-messages.controller.js +223 -0
- package/dist/backend/backend/src/controllers/messaging/scheduled-messages.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/monitoring/file-watcher.controller.d.ts +121 -0
- package/dist/backend/backend/src/controllers/monitoring/file-watcher.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/monitoring/file-watcher.controller.js +392 -0
- package/dist/backend/backend/src/controllers/monitoring/file-watcher.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/monitoring/monitoring.routes.d.ts +9 -0
- package/dist/backend/backend/src/controllers/monitoring/monitoring.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/monitoring/monitoring.routes.js +15 -0
- package/dist/backend/backend/src/controllers/monitoring/monitoring.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts +117 -0
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js +705 -0
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/orchestrator/orchestrator.controller.d.ts +22 -0
- package/dist/backend/backend/src/controllers/orchestrator/orchestrator.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/orchestrator/orchestrator.controller.js +465 -0
- package/dist/backend/backend/src/controllers/orchestrator/orchestrator.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/orchestrator/orchestrator.routes.d.ts +9 -0
- package/dist/backend/backend/src/controllers/orchestrator/orchestrator.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/orchestrator/orchestrator.routes.js +26 -0
- package/dist/backend/backend/src/controllers/orchestrator/orchestrator.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/project/git.controller.d.ts +10 -0
- package/dist/backend/backend/src/controllers/project/git.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/project/git.controller.js +160 -0
- package/dist/backend/backend/src/controllers/project/git.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/project/project.controller.d.ts +41 -0
- package/dist/backend/backend/src/controllers/project/project.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/project/project.controller.js +1126 -0
- package/dist/backend/backend/src/controllers/project/project.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/project/project.routes.d.ts +19 -0
- package/dist/backend/backend/src/controllers/project/project.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/project/project.routes.js +141 -0
- package/dist/backend/backend/src/controllers/project/project.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/quality-gate/quality-gate.controller.d.ts +32 -0
- package/dist/backend/backend/src/controllers/quality-gate/quality-gate.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/quality-gate/quality-gate.controller.js +57 -0
- package/dist/backend/backend/src/controllers/quality-gate/quality-gate.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/request-types.d.ts +225 -0
- package/dist/backend/backend/src/controllers/request-types.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/request-types.js +8 -0
- package/dist/backend/backend/src/controllers/request-types.js.map +1 -0
- package/dist/backend/backend/src/controllers/self-improvement/index.d.ts +9 -0
- package/dist/backend/backend/src/controllers/self-improvement/index.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/self-improvement/index.js +9 -0
- package/dist/backend/backend/src/controllers/self-improvement/index.js.map +1 -0
- package/dist/backend/backend/src/controllers/self-improvement/self-improvement.controller.d.ts +11 -0
- package/dist/backend/backend/src/controllers/self-improvement/self-improvement.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/self-improvement/self-improvement.controller.js +166 -0
- package/dist/backend/backend/src/controllers/self-improvement/self-improvement.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/session/session.controller.d.ts +80 -0
- package/dist/backend/backend/src/controllers/session/session.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/session/session.controller.js +275 -0
- package/dist/backend/backend/src/controllers/session/session.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/session/session.routes.d.ts +19 -0
- package/dist/backend/backend/src/controllers/session/session.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/session/session.routes.js +33 -0
- package/dist/backend/backend/src/controllers/session/session.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/settings/index.d.ts +18 -0
- package/dist/backend/backend/src/controllers/settings/index.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/settings/index.js +27 -0
- package/dist/backend/backend/src/controllers/settings/index.js.map +1 -0
- package/dist/backend/backend/src/controllers/settings/role.controller.d.ts +10 -0
- package/dist/backend/backend/src/controllers/settings/role.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/settings/role.controller.js +365 -0
- package/dist/backend/backend/src/controllers/settings/role.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/settings/settings.controller.d.ts +10 -0
- package/dist/backend/backend/src/controllers/settings/settings.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/settings/settings.controller.js +176 -0
- package/dist/backend/backend/src/controllers/settings/settings.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/skill/index.d.ts +17 -0
- package/dist/backend/backend/src/controllers/skill/index.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/skill/index.js +18 -0
- package/dist/backend/backend/src/controllers/skill/index.js.map +1 -0
- package/dist/backend/backend/src/controllers/skill/skill.controller.d.ts +10 -0
- package/dist/backend/backend/src/controllers/skill/skill.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/skill/skill.controller.js +423 -0
- package/dist/backend/backend/src/controllers/skill/skill.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/slack/index.d.ts +17 -0
- package/dist/backend/backend/src/controllers/slack/index.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/slack/index.js +18 -0
- package/dist/backend/backend/src/controllers/slack/index.js.map +1 -0
- package/dist/backend/backend/src/controllers/slack/slack-thread.controller.d.ts +22 -0
- package/dist/backend/backend/src/controllers/slack/slack-thread.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/slack/slack-thread.controller.js +59 -0
- package/dist/backend/backend/src/controllers/slack/slack-thread.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/slack/slack-thread.routes.d.ts +15 -0
- package/dist/backend/backend/src/controllers/slack/slack-thread.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/slack/slack-thread.routes.js +21 -0
- package/dist/backend/backend/src/controllers/slack/slack-thread.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts +11 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +416 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/system/config.controller.d.ts +4 -0
- package/dist/backend/backend/src/controllers/system/config.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/system/config.controller.js +48 -0
- package/dist/backend/backend/src/controllers/system/config.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/system/errors.controller.d.ts +8 -0
- package/dist/backend/backend/src/controllers/system/errors.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/system/errors.controller.js +99 -0
- package/dist/backend/backend/src/controllers/system/errors.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/system/scheduler.controller.d.ts +13 -0
- package/dist/backend/backend/src/controllers/system/scheduler.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/system/scheduler.controller.js +63 -0
- package/dist/backend/backend/src/controllers/system/scheduler.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/system/system.controller.d.ts +67 -0
- package/dist/backend/backend/src/controllers/system/system.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/system/system.controller.js +390 -0
- package/dist/backend/backend/src/controllers/system/system.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/system/system.routes.d.ts +9 -0
- package/dist/backend/backend/src/controllers/system/system.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/system/system.routes.js +16 -0
- package/dist/backend/backend/src/controllers/system/system.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/task-management/assignments.controller.d.ts +5 -0
- package/dist/backend/backend/src/controllers/task-management/assignments.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/task-management/assignments.controller.js +56 -0
- package/dist/backend/backend/src/controllers/task-management/assignments.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/task-management/in-progress-tasks.controller.d.ts +10 -0
- package/dist/backend/backend/src/controllers/task-management/in-progress-tasks.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/task-management/in-progress-tasks.controller.js +43 -0
- package/dist/backend/backend/src/controllers/task-management/in-progress-tasks.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.d.ts +95 -0
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.js +1034 -0
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/task-management/tasks.controller.d.ts +8 -0
- package/dist/backend/backend/src/controllers/task-management/tasks.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/task-management/tasks.controller.js +117 -0
- package/dist/backend/backend/src/controllers/task-management/tasks.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/task-management/tickets.controller.d.ts +13 -0
- package/dist/backend/backend/src/controllers/task-management/tickets.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/task-management/tickets.controller.js +212 -0
- package/dist/backend/backend/src/controllers/task-management/tickets.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/team/team.controller.d.ts +35 -0
- package/dist/backend/backend/src/controllers/team/team.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/team/team.controller.js +1629 -0
- package/dist/backend/backend/src/controllers/team/team.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/team/team.routes.d.ts +9 -0
- package/dist/backend/backend/src/controllers/team/team.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/team/team.routes.js +41 -0
- package/dist/backend/backend/src/controllers/team/team.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/teams-backup/teams-backup.controller.d.ts +32 -0
- package/dist/backend/backend/src/controllers/teams-backup/teams-backup.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/teams-backup/teams-backup.controller.js +99 -0
- package/dist/backend/backend/src/controllers/teams-backup/teams-backup.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/teams-backup/teams-backup.routes.d.ts +15 -0
- package/dist/backend/backend/src/controllers/teams-backup/teams-backup.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/teams-backup/teams-backup.routes.js +23 -0
- package/dist/backend/backend/src/controllers/teams-backup/teams-backup.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/types.d.ts +18 -0
- package/dist/backend/backend/src/controllers/types.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/types.js +2 -0
- package/dist/backend/backend/src/controllers/types.js.map +1 -0
- package/dist/backend/backend/src/controllers/utils/file-utils.d.ts +3 -0
- package/dist/backend/backend/src/controllers/utils/file-utils.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/utils/file-utils.js +98 -0
- package/dist/backend/backend/src/controllers/utils/file-utils.js.map +1 -0
- package/dist/backend/backend/src/index.d.ts +60 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -0
- package/dist/backend/backend/src/index.js +872 -0
- package/dist/backend/backend/src/index.js.map +1 -0
- package/dist/backend/backend/src/middleware/agent-heartbeat.middleware.d.ts +22 -0
- package/dist/backend/backend/src/middleware/agent-heartbeat.middleware.d.ts.map +1 -0
- package/dist/backend/backend/src/middleware/agent-heartbeat.middleware.js +36 -0
- package/dist/backend/backend/src/middleware/agent-heartbeat.middleware.js.map +1 -0
- package/dist/backend/backend/src/models/Project.d.ts +18 -0
- package/dist/backend/backend/src/models/Project.d.ts.map +1 -0
- package/dist/backend/backend/src/models/Project.js +70 -0
- package/dist/backend/backend/src/models/Project.js.map +1 -0
- package/dist/backend/backend/src/models/ScheduledMessage.d.ts +27 -0
- package/dist/backend/backend/src/models/ScheduledMessage.d.ts.map +1 -0
- package/dist/backend/backend/src/models/ScheduledMessage.js +50 -0
- package/dist/backend/backend/src/models/ScheduledMessage.js.map +1 -0
- package/dist/backend/backend/src/models/Team.d.ts +20 -0
- package/dist/backend/backend/src/models/Team.d.ts.map +1 -0
- package/dist/backend/backend/src/models/Team.js +120 -0
- package/dist/backend/backend/src/models/Team.js.map +1 -0
- package/dist/backend/backend/src/models/Ticket.d.ts +24 -0
- package/dist/backend/backend/src/models/Ticket.d.ts.map +1 -0
- package/dist/backend/backend/src/models/Ticket.js +102 -0
- package/dist/backend/backend/src/models/Ticket.js.map +1 -0
- package/dist/backend/backend/src/models/index.d.ts +5 -0
- package/dist/backend/backend/src/models/index.d.ts.map +1 -0
- package/dist/backend/backend/src/models/index.js +5 -0
- package/dist/backend/backend/src/models/index.js.map +1 -0
- package/dist/backend/backend/src/routes/api.routes.d.ts +9 -0
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/routes/api.routes.js +81 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -0
- package/dist/backend/backend/src/routes/factory.routes.d.ts +19 -0
- package/dist/backend/backend/src/routes/factory.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/routes/factory.routes.js +103 -0
- package/dist/backend/backend/src/routes/factory.routes.js.map +1 -0
- package/dist/backend/backend/src/routes/modules/assignments.routes.d.ts +4 -0
- package/dist/backend/backend/src/routes/modules/assignments.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/routes/modules/assignments.routes.js +7 -0
- package/dist/backend/backend/src/routes/modules/assignments.routes.js.map +1 -0
- package/dist/backend/backend/src/routes/modules/config.routes.d.ts +4 -0
- package/dist/backend/backend/src/routes/modules/config.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/routes/modules/config.routes.js +6 -0
- package/dist/backend/backend/src/routes/modules/config.routes.js.map +1 -0
- package/dist/backend/backend/src/routes/modules/delivery-logs.routes.d.ts +4 -0
- package/dist/backend/backend/src/routes/modules/delivery-logs.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/routes/modules/delivery-logs.routes.js +7 -0
- package/dist/backend/backend/src/routes/modules/delivery-logs.routes.js.map +1 -0
- package/dist/backend/backend/src/routes/modules/errors.routes.d.ts +4 -0
- package/dist/backend/backend/src/routes/modules/errors.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/routes/modules/errors.routes.js +10 -0
- package/dist/backend/backend/src/routes/modules/errors.routes.js.map +1 -0
- package/dist/backend/backend/src/routes/modules/quality-gate.routes.d.ts +20 -0
- package/dist/backend/backend/src/routes/modules/quality-gate.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/routes/modules/quality-gate.routes.js +25 -0
- package/dist/backend/backend/src/routes/modules/quality-gate.routes.js.map +1 -0
- package/dist/backend/backend/src/routes/modules/scheduled-messages.routes.d.ts +4 -0
- package/dist/backend/backend/src/routes/modules/scheduled-messages.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/routes/modules/scheduled-messages.routes.js +12 -0
- package/dist/backend/backend/src/routes/modules/scheduled-messages.routes.js.map +1 -0
- package/dist/backend/backend/src/routes/modules/scheduler.routes.d.ts +4 -0
- package/dist/backend/backend/src/routes/modules/scheduler.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/routes/modules/scheduler.routes.js +9 -0
- package/dist/backend/backend/src/routes/modules/scheduler.routes.js.map +1 -0
- package/dist/backend/backend/src/routes/modules/system.routes.d.ts +4 -0
- package/dist/backend/backend/src/routes/modules/system.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/routes/modules/system.routes.js +24 -0
- package/dist/backend/backend/src/routes/modules/system.routes.js.map +1 -0
- package/dist/backend/backend/src/routes/modules/task-management.routes.d.ts +4 -0
- package/dist/backend/backend/src/routes/modules/task-management.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/routes/modules/task-management.routes.js +25 -0
- package/dist/backend/backend/src/routes/modules/task-management.routes.js.map +1 -0
- package/dist/backend/backend/src/routes/modules/terminal.routes.d.ts +29 -0
- package/dist/backend/backend/src/routes/modules/terminal.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/routes/modules/terminal.routes.js +49 -0
- package/dist/backend/backend/src/routes/modules/terminal.routes.js.map +1 -0
- package/dist/backend/backend/src/services/agent/agent-heartbeat.service.d.ts +240 -0
- package/dist/backend/backend/src/services/agent/agent-heartbeat.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/agent-heartbeat.service.js +524 -0
- package/dist/backend/backend/src/services/agent/agent-heartbeat.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +398 -0
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +2863 -0
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/agent-suspend.service.d.ts +118 -0
- package/dist/backend/backend/src/services/agent/agent-suspend.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/agent-suspend.service.js +304 -0
- package/dist/backend/backend/src/services/agent/agent-suspend.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/claude-runtime.service.d.ts +71 -0
- package/dist/backend/backend/src/services/agent/claude-runtime.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/claude-runtime.service.js +220 -0
- package/dist/backend/backend/src/services/agent/claude-runtime.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/codex-runtime.service.d.ts +43 -0
- package/dist/backend/backend/src/services/agent/codex-runtime.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/codex-runtime.service.js +118 -0
- package/dist/backend/backend/src/services/agent/codex-runtime.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/disk-cleanup.service.d.ts +79 -0
- package/dist/backend/backend/src/services/agent/disk-cleanup.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/disk-cleanup.service.js +137 -0
- package/dist/backend/backend/src/services/agent/disk-cleanup.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/file-watcher.service.d.ts +87 -0
- package/dist/backend/backend/src/services/agent/file-watcher.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/file-watcher.service.js +312 -0
- package/dist/backend/backend/src/services/agent/file-watcher.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/gemini-runtime.service.d.ts +83 -0
- package/dist/backend/backend/src/services/agent/gemini-runtime.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/gemini-runtime.service.js +300 -0
- package/dist/backend/backend/src/services/agent/gemini-runtime.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/git-integration.service.d.ts +131 -0
- package/dist/backend/backend/src/services/agent/git-integration.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/git-integration.service.js +368 -0
- package/dist/backend/backend/src/services/agent/git-integration.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/idle-detection.service.d.ts +54 -0
- package/dist/backend/backend/src/services/agent/idle-detection.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/idle-detection.service.js +156 -0
- package/dist/backend/backend/src/services/agent/idle-detection.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/pty-activity-tracker.service.d.ts +77 -0
- package/dist/backend/backend/src/services/agent/pty-activity-tracker.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/pty-activity-tracker.service.js +104 -0
- package/dist/backend/backend/src/services/agent/pty-activity-tracker.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts +114 -0
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +437 -0
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -0
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.d.ts +81 -0
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js +284 -0
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/runtime-service.factory.d.ts +71 -0
- package/dist/backend/backend/src/services/agent/runtime-service.factory.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/runtime-service.factory.js +165 -0
- package/dist/backend/backend/src/services/agent/runtime-service.factory.js.map +1 -0
- package/dist/backend/backend/src/services/agent/tmux-command.service.d.ts +163 -0
- package/dist/backend/backend/src/services/agent/tmux-command.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/tmux-command.service.js +962 -0
- package/dist/backend/backend/src/services/agent/tmux-command.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/tmux.service.d.ts +189 -0
- package/dist/backend/backend/src/services/agent/tmux.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/tmux.service.js +673 -0
- package/dist/backend/backend/src/services/agent/tmux.service.js.map +1 -0
- package/dist/backend/backend/src/services/ai/context-loader.service.d.ts +40 -0
- package/dist/backend/backend/src/services/ai/context-loader.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/ai/context-loader.service.js +311 -0
- package/dist/backend/backend/src/services/ai/context-loader.service.js.map +1 -0
- package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts +183 -0
- package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/ai/prompt-builder.service.js +591 -0
- package/dist/backend/backend/src/services/ai/prompt-builder.service.js.map +1 -0
- package/dist/backend/backend/src/services/ai/prompt-template.service.d.ts +51 -0
- package/dist/backend/backend/src/services/ai/prompt-template.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/ai/prompt-template.service.js +99 -0
- package/dist/backend/backend/src/services/ai/prompt-template.service.js.map +1 -0
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.d.ts +429 -0
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.js +852 -0
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.js.map +1 -0
- package/dist/backend/backend/src/services/autonomous/budget.service.d.ts +319 -0
- package/dist/backend/backend/src/services/autonomous/budget.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/autonomous/budget.service.js +594 -0
- package/dist/backend/backend/src/services/autonomous/budget.service.js.map +1 -0
- package/dist/backend/backend/src/services/autonomous/index.d.ts +11 -0
- package/dist/backend/backend/src/services/autonomous/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/autonomous/index.js +11 -0
- package/dist/backend/backend/src/services/autonomous/index.js.map +1 -0
- package/dist/backend/backend/src/services/chat/chat.service.d.ts +358 -0
- package/dist/backend/backend/src/services/chat/chat.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat/chat.service.js +727 -0
- package/dist/backend/backend/src/services/chat/chat.service.js.map +1 -0
- package/dist/backend/backend/src/services/chat/index.d.ts +10 -0
- package/dist/backend/backend/src/services/chat/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat/index.js +10 -0
- package/dist/backend/backend/src/services/chat/index.js.map +1 -0
- package/dist/backend/backend/src/services/continuation/continuation-events.service.d.ts +152 -0
- package/dist/backend/backend/src/services/continuation/continuation-events.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/continuation/continuation-events.service.js +293 -0
- package/dist/backend/backend/src/services/continuation/continuation-events.service.js.map +1 -0
- package/dist/backend/backend/src/services/continuation/continuation.service.d.ts +278 -0
- package/dist/backend/backend/src/services/continuation/continuation.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/continuation/continuation.service.js +616 -0
- package/dist/backend/backend/src/services/continuation/continuation.service.js.map +1 -0
- package/dist/backend/backend/src/services/continuation/index.d.ts +14 -0
- package/dist/backend/backend/src/services/continuation/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/continuation/index.js +15 -0
- package/dist/backend/backend/src/services/continuation/index.js.map +1 -0
- package/dist/backend/backend/src/services/continuation/output-analyzer.service.d.ts +199 -0
- package/dist/backend/backend/src/services/continuation/output-analyzer.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/continuation/output-analyzer.service.js +390 -0
- package/dist/backend/backend/src/services/continuation/output-analyzer.service.js.map +1 -0
- package/dist/backend/backend/src/services/continuation/patterns/completion-patterns.d.ts +44 -0
- package/dist/backend/backend/src/services/continuation/patterns/completion-patterns.d.ts.map +1 -0
- package/dist/backend/backend/src/services/continuation/patterns/completion-patterns.js +82 -0
- package/dist/backend/backend/src/services/continuation/patterns/completion-patterns.js.map +1 -0
- package/dist/backend/backend/src/services/continuation/patterns/error-patterns.d.ts +37 -0
- package/dist/backend/backend/src/services/continuation/patterns/error-patterns.d.ts.map +1 -0
- package/dist/backend/backend/src/services/continuation/patterns/error-patterns.js +105 -0
- package/dist/backend/backend/src/services/continuation/patterns/error-patterns.js.map +1 -0
- package/dist/backend/backend/src/services/continuation/patterns/idle-patterns.d.ts +29 -0
- package/dist/backend/backend/src/services/continuation/patterns/idle-patterns.d.ts.map +1 -0
- package/dist/backend/backend/src/services/continuation/patterns/idle-patterns.js +49 -0
- package/dist/backend/backend/src/services/continuation/patterns/idle-patterns.js.map +1 -0
- package/dist/backend/backend/src/services/continuation/patterns/index.d.ts +12 -0
- package/dist/backend/backend/src/services/continuation/patterns/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/continuation/patterns/index.js +12 -0
- package/dist/backend/backend/src/services/continuation/patterns/index.js.map +1 -0
- package/dist/backend/backend/src/services/continuation/patterns/waiting-patterns.d.ts +34 -0
- package/dist/backend/backend/src/services/continuation/patterns/waiting-patterns.d.ts.map +1 -0
- package/dist/backend/backend/src/services/continuation/patterns/waiting-patterns.js +64 -0
- package/dist/backend/backend/src/services/continuation/patterns/waiting-patterns.js.map +1 -0
- package/dist/backend/backend/src/services/continuation/template-loader.service.d.ts +196 -0
- package/dist/backend/backend/src/services/continuation/template-loader.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/continuation/template-loader.service.js +323 -0
- package/dist/backend/backend/src/services/continuation/template-loader.service.js.map +1 -0
- package/dist/backend/backend/src/services/core/config.service.d.ts +85 -0
- package/dist/backend/backend/src/services/core/config.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/core/config.service.js +226 -0
- package/dist/backend/backend/src/services/core/config.service.js.map +1 -0
- package/dist/backend/backend/src/services/core/error-tracking.service.d.ts +98 -0
- package/dist/backend/backend/src/services/core/error-tracking.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/core/error-tracking.service.js +291 -0
- package/dist/backend/backend/src/services/core/error-tracking.service.js.map +1 -0
- package/dist/backend/backend/src/services/core/logger.service.d.ts +70 -0
- package/dist/backend/backend/src/services/core/logger.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/core/logger.service.js +350 -0
- package/dist/backend/backend/src/services/core/logger.service.js.map +1 -0
- package/dist/backend/backend/src/services/core/storage.service.d.ts +261 -0
- package/dist/backend/backend/src/services/core/storage.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/core/storage.service.js +1390 -0
- package/dist/backend/backend/src/services/core/storage.service.js.map +1 -0
- package/dist/backend/backend/src/services/core/teams-backup.service.d.ts +92 -0
- package/dist/backend/backend/src/services/core/teams-backup.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/core/teams-backup.service.js +120 -0
- package/dist/backend/backend/src/services/core/teams-backup.service.js.map +1 -0
- package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts +144 -0
- package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/event-bus/event-bus.service.js +337 -0
- package/dist/backend/backend/src/services/event-bus/event-bus.service.js.map +1 -0
- package/dist/backend/backend/src/services/event-bus/index.d.ts +7 -0
- package/dist/backend/backend/src/services/event-bus/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/event-bus/index.js +7 -0
- package/dist/backend/backend/src/services/event-bus/index.js.map +1 -0
- package/dist/backend/backend/src/services/factory/factory-sse.service.d.ts +163 -0
- package/dist/backend/backend/src/services/factory/factory-sse.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/factory/factory-sse.service.js +274 -0
- package/dist/backend/backend/src/services/factory/factory-sse.service.js.map +1 -0
- package/dist/backend/backend/src/services/factory.service.d.ts +157 -0
- package/dist/backend/backend/src/services/factory.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/factory.service.js +545 -0
- package/dist/backend/backend/src/services/factory.service.js.map +1 -0
- package/dist/backend/backend/src/services/index.d.ts +37 -0
- package/dist/backend/backend/src/services/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/index.js +50 -0
- package/dist/backend/backend/src/services/index.js.map +1 -0
- package/dist/backend/backend/src/services/knowledge/index.d.ts +7 -0
- package/dist/backend/backend/src/services/knowledge/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/knowledge/index.js +7 -0
- package/dist/backend/backend/src/services/knowledge/index.js.map +1 -0
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.d.ts +125 -0
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js +247 -0
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js.map +1 -0
- package/dist/backend/backend/src/services/knowledge/knowledge.service.d.ts +153 -0
- package/dist/backend/backend/src/services/knowledge/knowledge.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/knowledge/knowledge.service.js +409 -0
- package/dist/backend/backend/src/services/knowledge/knowledge.service.js.map +1 -0
- package/dist/backend/backend/src/services/marketplace/index.d.ts +11 -0
- package/dist/backend/backend/src/services/marketplace/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/marketplace/index.js +13 -0
- package/dist/backend/backend/src/services/marketplace/index.js.map +1 -0
- package/dist/backend/backend/src/services/marketplace/marketplace-installer.service.d.ts +47 -0
- package/dist/backend/backend/src/services/marketplace/marketplace-installer.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/marketplace/marketplace-installer.service.js +127 -0
- package/dist/backend/backend/src/services/marketplace/marketplace-installer.service.js.map +1 -0
- package/dist/backend/backend/src/services/marketplace/marketplace.service.d.ts +101 -0
- package/dist/backend/backend/src/services/marketplace/marketplace.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/marketplace/marketplace.service.js +207 -0
- package/dist/backend/backend/src/services/marketplace/marketplace.service.js.map +1 -0
- package/dist/backend/backend/src/services/memory/agent-memory.service.d.ts +259 -0
- package/dist/backend/backend/src/services/memory/agent-memory.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/memory/agent-memory.service.js +539 -0
- package/dist/backend/backend/src/services/memory/agent-memory.service.js.map +1 -0
- package/dist/backend/backend/src/services/memory/daily-log.service.d.ts +159 -0
- package/dist/backend/backend/src/services/memory/daily-log.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/memory/daily-log.service.js +272 -0
- package/dist/backend/backend/src/services/memory/daily-log.service.js.map +1 -0
- package/dist/backend/backend/src/services/memory/goal-tracking.service.d.ts +239 -0
- package/dist/backend/backend/src/services/memory/goal-tracking.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/memory/goal-tracking.service.js +353 -0
- package/dist/backend/backend/src/services/memory/goal-tracking.service.js.map +1 -0
- package/dist/backend/backend/src/services/memory/index.d.ts +16 -0
- package/dist/backend/backend/src/services/memory/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/memory/index.js +15 -0
- package/dist/backend/backend/src/services/memory/index.js.map +1 -0
- package/dist/backend/backend/src/services/memory/learning-accumulation.service.d.ts +228 -0
- package/dist/backend/backend/src/services/memory/learning-accumulation.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/memory/learning-accumulation.service.js +336 -0
- package/dist/backend/backend/src/services/memory/learning-accumulation.service.js.map +1 -0
- package/dist/backend/backend/src/services/memory/memory.service.d.ts +306 -0
- package/dist/backend/backend/src/services/memory/memory.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/memory/memory.service.js +517 -0
- package/dist/backend/backend/src/services/memory/memory.service.js.map +1 -0
- package/dist/backend/backend/src/services/memory/project-memory.service.d.ts +252 -0
- package/dist/backend/backend/src/services/memory/project-memory.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/memory/project-memory.service.js +600 -0
- package/dist/backend/backend/src/services/memory/project-memory.service.js.map +1 -0
- package/dist/backend/backend/src/services/memory/session-memory.service.d.ts +197 -0
- package/dist/backend/backend/src/services/memory/session-memory.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/memory/session-memory.service.js +369 -0
- package/dist/backend/backend/src/services/memory/session-memory.service.js.map +1 -0
- package/dist/backend/backend/src/services/messaging/index.d.ts +9 -0
- package/dist/backend/backend/src/services/messaging/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/messaging/index.js +9 -0
- package/dist/backend/backend/src/services/messaging/index.js.map +1 -0
- package/dist/backend/backend/src/services/messaging/message-queue.service.d.ts +198 -0
- package/dist/backend/backend/src/services/messaging/message-queue.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/messaging/message-queue.service.js +445 -0
- package/dist/backend/backend/src/services/messaging/message-queue.service.js.map +1 -0
- package/dist/backend/backend/src/services/messaging/queue-processor.service.d.ts +90 -0
- package/dist/backend/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/messaging/queue-processor.service.js +324 -0
- package/dist/backend/backend/src/services/messaging/queue-processor.service.js.map +1 -0
- package/dist/backend/backend/src/services/messaging/response-router.service.d.ts +56 -0
- package/dist/backend/backend/src/services/messaging/response-router.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/messaging/response-router.service.js +134 -0
- package/dist/backend/backend/src/services/messaging/response-router.service.js.map +1 -0
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +84 -0
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js +136 -0
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -0
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.d.ts +113 -0
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.js +473 -0
- package/dist/backend/backend/src/services/monitoring/activity-monitor.service.js.map +1 -0
- package/dist/backend/backend/src/services/monitoring/monitoring.service.d.ts +129 -0
- package/dist/backend/backend/src/services/monitoring/monitoring.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/monitoring/monitoring.service.js +469 -0
- package/dist/backend/backend/src/services/monitoring/monitoring.service.js.map +1 -0
- package/dist/backend/backend/src/services/monitoring/system-resource-alert.service.d.ts +48 -0
- package/dist/backend/backend/src/services/monitoring/system-resource-alert.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/monitoring/system-resource-alert.service.js +151 -0
- package/dist/backend/backend/src/services/monitoring/system-resource-alert.service.js.map +1 -0
- package/dist/backend/backend/src/services/monitoring/task-assignment-monitor.service.d.ts +71 -0
- package/dist/backend/backend/src/services/monitoring/task-assignment-monitor.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/monitoring/task-assignment-monitor.service.js +304 -0
- package/dist/backend/backend/src/services/monitoring/task-assignment-monitor.service.js.map +1 -0
- package/dist/backend/backend/src/services/monitoring/team-activity-websocket.service.d.ts +100 -0
- package/dist/backend/backend/src/services/monitoring/team-activity-websocket.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/monitoring/team-activity-websocket.service.js +285 -0
- package/dist/backend/backend/src/services/monitoring/team-activity-websocket.service.js.map +1 -0
- package/dist/backend/backend/src/services/monitoring/teams-json-watcher.service.d.ts +107 -0
- package/dist/backend/backend/src/services/monitoring/teams-json-watcher.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/monitoring/teams-json-watcher.service.js +568 -0
- package/dist/backend/backend/src/services/monitoring/teams-json-watcher.service.js.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/improvement-marker.service.d.ts +231 -0
- package/dist/backend/backend/src/services/orchestrator/improvement-marker.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/improvement-marker.service.js +344 -0
- package/dist/backend/backend/src/services/orchestrator/improvement-marker.service.js.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/improvement-startup.service.d.ts +156 -0
- package/dist/backend/backend/src/services/orchestrator/improvement-startup.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/improvement-startup.service.js +481 -0
- package/dist/backend/backend/src/services/orchestrator/improvement-startup.service.js.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/index.d.ts +15 -0
- package/dist/backend/backend/src/services/orchestrator/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/index.js +15 -0
- package/dist/backend/backend/src/services/orchestrator/index.js.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/orchestrator-restart.service.d.ts +112 -0
- package/dist/backend/backend/src/services/orchestrator/orchestrator-restart.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/orchestrator-restart.service.js +244 -0
- package/dist/backend/backend/src/services/orchestrator/orchestrator-restart.service.js.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.d.ts +61 -0
- package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.js +156 -0
- package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.js.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/safe-restart.service.d.ts +191 -0
- package/dist/backend/backend/src/services/orchestrator/safe-restart.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/safe-restart.service.js +393 -0
- package/dist/backend/backend/src/services/orchestrator/safe-restart.service.js.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/self-improvement.service.d.ts +200 -0
- package/dist/backend/backend/src/services/orchestrator/self-improvement.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/self-improvement.service.js +394 -0
- package/dist/backend/backend/src/services/orchestrator/self-improvement.service.js.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/state-persistence.service.d.ts +226 -0
- package/dist/backend/backend/src/services/orchestrator/state-persistence.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/state-persistence.service.js +504 -0
- package/dist/backend/backend/src/services/orchestrator/state-persistence.service.js.map +1 -0
- package/dist/backend/backend/src/services/project/active-projects.service.d.ts +39 -0
- package/dist/backend/backend/src/services/project/active-projects.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/project/active-projects.service.js +295 -0
- package/dist/backend/backend/src/services/project/active-projects.service.js.map +1 -0
- package/dist/backend/backend/src/services/project/task-folder.service.d.ts +48 -0
- package/dist/backend/backend/src/services/project/task-folder.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/project/task-folder.service.js +193 -0
- package/dist/backend/backend/src/services/project/task-folder.service.js.map +1 -0
- package/dist/backend/backend/src/services/project/task-tracking.service.d.ts +47 -0
- package/dist/backend/backend/src/services/project/task-tracking.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/project/task-tracking.service.js +330 -0
- package/dist/backend/backend/src/services/project/task-tracking.service.js.map +1 -0
- package/dist/backend/backend/src/services/project/task.service.d.ts +31 -0
- package/dist/backend/backend/src/services/project/task.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/project/task.service.js +193 -0
- package/dist/backend/backend/src/services/project/task.service.js.map +1 -0
- package/dist/backend/backend/src/services/project/ticket-editor.service.d.ts +129 -0
- package/dist/backend/backend/src/services/project/ticket-editor.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/project/ticket-editor.service.js +405 -0
- package/dist/backend/backend/src/services/project/ticket-editor.service.js.map +1 -0
- package/dist/backend/backend/src/services/prompt/index.d.ts +7 -0
- package/dist/backend/backend/src/services/prompt/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/prompt/index.js +7 -0
- package/dist/backend/backend/src/services/prompt/index.js.map +1 -0
- package/dist/backend/backend/src/services/prompt/prompt-generator.service.d.ts +106 -0
- package/dist/backend/backend/src/services/prompt/prompt-generator.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/prompt/prompt-generator.service.js +287 -0
- package/dist/backend/backend/src/services/prompt/prompt-generator.service.js.map +1 -0
- package/dist/backend/backend/src/services/quality/index.d.ts +10 -0
- package/dist/backend/backend/src/services/quality/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/quality/index.js +10 -0
- package/dist/backend/backend/src/services/quality/index.js.map +1 -0
- package/dist/backend/backend/src/services/quality/quality-gate.service.d.ts +223 -0
- package/dist/backend/backend/src/services/quality/quality-gate.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/quality/quality-gate.service.js +388 -0
- package/dist/backend/backend/src/services/quality/quality-gate.service.js.map +1 -0
- package/dist/backend/backend/src/services/session/index.d.ts +43 -0
- package/dist/backend/backend/src/services/session/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/session/index.js +46 -0
- package/dist/backend/backend/src/services/session/index.js.map +1 -0
- package/dist/backend/backend/src/services/session/pty/index.d.ts +11 -0
- package/dist/backend/backend/src/services/session/pty/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/session/pty/index.js +11 -0
- package/dist/backend/backend/src/services/session/pty/index.js.map +1 -0
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.d.ts +258 -0
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.d.ts.map +1 -0
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.js +435 -0
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.js.map +1 -0
- package/dist/backend/backend/src/services/session/pty/pty-session.d.ts +196 -0
- package/dist/backend/backend/src/services/session/pty/pty-session.d.ts.map +1 -0
- package/dist/backend/backend/src/services/session/pty/pty-session.js +393 -0
- package/dist/backend/backend/src/services/session/pty/pty-session.js.map +1 -0
- package/dist/backend/backend/src/services/session/pty/pty-terminal-buffer.d.ts +241 -0
- package/dist/backend/backend/src/services/session/pty/pty-terminal-buffer.d.ts.map +1 -0
- package/dist/backend/backend/src/services/session/pty/pty-terminal-buffer.js +351 -0
- package/dist/backend/backend/src/services/session/pty/pty-terminal-buffer.js.map +1 -0
- package/dist/backend/backend/src/services/session/session-backend.factory.d.ts +127 -0
- package/dist/backend/backend/src/services/session/session-backend.factory.d.ts.map +1 -0
- package/dist/backend/backend/src/services/session/session-backend.factory.js +226 -0
- package/dist/backend/backend/src/services/session/session-backend.factory.js.map +1 -0
- package/dist/backend/backend/src/services/session/session-backend.interface.d.ts +358 -0
- package/dist/backend/backend/src/services/session/session-backend.interface.d.ts.map +1 -0
- package/dist/backend/backend/src/services/session/session-backend.interface.js +19 -0
- package/dist/backend/backend/src/services/session/session-backend.interface.js.map +1 -0
- package/dist/backend/backend/src/services/session/session-command-helper.d.ts +327 -0
- package/dist/backend/backend/src/services/session/session-command-helper.d.ts.map +1 -0
- package/dist/backend/backend/src/services/session/session-command-helper.js +687 -0
- package/dist/backend/backend/src/services/session/session-command-helper.js.map +1 -0
- package/dist/backend/backend/src/services/session/session-state-persistence.d.ts +228 -0
- package/dist/backend/backend/src/services/session/session-state-persistence.d.ts.map +1 -0
- package/dist/backend/backend/src/services/session/session-state-persistence.js +394 -0
- package/dist/backend/backend/src/services/session/session-state-persistence.js.map +1 -0
- package/dist/backend/backend/src/services/settings/index.d.ts +10 -0
- package/dist/backend/backend/src/services/settings/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/settings/index.js +12 -0
- package/dist/backend/backend/src/services/settings/index.js.map +1 -0
- package/dist/backend/backend/src/services/settings/role.service.d.ts +276 -0
- package/dist/backend/backend/src/services/settings/role.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/settings/role.service.js +726 -0
- package/dist/backend/backend/src/services/settings/role.service.js.map +1 -0
- package/dist/backend/backend/src/services/settings/settings.service.d.ts +160 -0
- package/dist/backend/backend/src/services/settings/settings.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/settings/settings.service.js +294 -0
- package/dist/backend/backend/src/services/settings/settings.service.js.map +1 -0
- package/dist/backend/backend/src/services/skill/index.d.ts +10 -0
- package/dist/backend/backend/src/services/skill/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/skill/index.js +10 -0
- package/dist/backend/backend/src/services/skill/index.js.map +1 -0
- package/dist/backend/backend/src/services/skill/skill-catalog.service.d.ts +236 -0
- package/dist/backend/backend/src/services/skill/skill-catalog.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/skill/skill-catalog.service.js +550 -0
- package/dist/backend/backend/src/services/skill/skill-catalog.service.js.map +1 -0
- package/dist/backend/backend/src/services/skill/skill-executor.service.d.ts +135 -0
- package/dist/backend/backend/src/services/skill/skill-executor.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/skill/skill-executor.service.js +493 -0
- package/dist/backend/backend/src/services/skill/skill-executor.service.js.map +1 -0
- package/dist/backend/backend/src/services/skill/skill.service.d.ts +241 -0
- package/dist/backend/backend/src/services/skill/skill.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/skill/skill.service.js +542 -0
- package/dist/backend/backend/src/services/skill/skill.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/index.d.ts +13 -0
- package/dist/backend/backend/src/services/slack/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/index.js +13 -0
- package/dist/backend/backend/src/services/slack/index.js.map +1 -0
- package/dist/backend/backend/src/services/slack/notify-reconciliation.service.d.ts +63 -0
- package/dist/backend/backend/src/services/slack/notify-reconciliation.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/notify-reconciliation.service.js +182 -0
- package/dist/backend/backend/src/services/slack/notify-reconciliation.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-image.service.d.ts +113 -0
- package/dist/backend/backend/src/services/slack/slack-image.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-image.service.js +329 -0
- package/dist/backend/backend/src/services/slack/slack-image.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts +71 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.js +106 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +302 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +806 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-thread-store.service.d.ts +147 -0
- package/dist/backend/backend/src/services/slack/slack-thread-store.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-thread-store.service.js +258 -0
- package/dist/backend/backend/src/services/slack/slack-thread-store.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +219 -0
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack.service.js +551 -0
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -0
- package/dist/backend/backend/src/services/sop/index.d.ts +9 -0
- package/dist/backend/backend/src/services/sop/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/sop/index.js +9 -0
- package/dist/backend/backend/src/services/sop/index.js.map +1 -0
- package/dist/backend/backend/src/services/sop/sop.service.d.ts +286 -0
- package/dist/backend/backend/src/services/sop/sop.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/sop/sop.service.js +583 -0
- package/dist/backend/backend/src/services/sop/sop.service.js.map +1 -0
- package/dist/backend/backend/src/services/system/version-check.service.d.ts +112 -0
- package/dist/backend/backend/src/services/system/version-check.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/system/version-check.service.js +195 -0
- package/dist/backend/backend/src/services/system/version-check.service.js.map +1 -0
- package/dist/backend/backend/src/services/workflow/message-scheduler.service.d.ts +99 -0
- package/dist/backend/backend/src/services/workflow/message-scheduler.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/workflow/message-scheduler.service.js +408 -0
- package/dist/backend/backend/src/services/workflow/message-scheduler.service.js.map +1 -0
- package/dist/backend/backend/src/services/workflow/scheduler.service.d.ts +300 -0
- package/dist/backend/backend/src/services/workflow/scheduler.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/workflow/scheduler.service.js +924 -0
- package/dist/backend/backend/src/services/workflow/scheduler.service.js.map +1 -0
- package/dist/backend/backend/src/types/auto-assign.types.d.ts +271 -0
- package/dist/backend/backend/src/types/auto-assign.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/auto-assign.types.js +136 -0
- package/dist/backend/backend/src/types/auto-assign.types.js.map +1 -0
- package/dist/backend/backend/src/types/budget.types.d.ts +217 -0
- package/dist/backend/backend/src/types/budget.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/budget.types.js +82 -0
- package/dist/backend/backend/src/types/budget.types.js.map +1 -0
- package/dist/backend/backend/src/types/chat.types.d.ts +550 -0
- package/dist/backend/backend/src/types/chat.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/chat.types.js +743 -0
- package/dist/backend/backend/src/types/chat.types.js.map +1 -0
- package/dist/backend/backend/src/types/continuation.types.d.ts +237 -0
- package/dist/backend/backend/src/types/continuation.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/continuation.types.js +10 -0
- package/dist/backend/backend/src/types/continuation.types.js.map +1 -0
- package/dist/backend/backend/src/types/event-bus.types.d.ts +112 -0
- package/dist/backend/backend/src/types/event-bus.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/event-bus.types.js +82 -0
- package/dist/backend/backend/src/types/event-bus.types.js.map +1 -0
- package/dist/backend/backend/src/types/index.d.ts +161 -0
- package/dist/backend/backend/src/types/index.d.ts.map +1 -0
- package/dist/backend/backend/src/types/index.js +23 -0
- package/dist/backend/backend/src/types/index.js.map +1 -0
- package/dist/backend/backend/src/types/knowledge.types.d.ts +195 -0
- package/dist/backend/backend/src/types/knowledge.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/knowledge.types.js +38 -0
- package/dist/backend/backend/src/types/knowledge.types.js.map +1 -0
- package/dist/backend/backend/src/types/marketplace.types.d.ts +68 -0
- package/dist/backend/backend/src/types/marketplace.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/marketplace.types.js +6 -0
- package/dist/backend/backend/src/types/marketplace.types.js.map +1 -0
- package/dist/backend/backend/src/types/memory.types.d.ts +587 -0
- package/dist/backend/backend/src/types/memory.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/memory.types.js +47 -0
- package/dist/backend/backend/src/types/memory.types.js.map +1 -0
- package/dist/backend/backend/src/types/messaging.types.d.ts +216 -0
- package/dist/backend/backend/src/types/messaging.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/messaging.types.js +224 -0
- package/dist/backend/backend/src/types/messaging.types.js.map +1 -0
- package/dist/backend/backend/src/types/orchestrator-state.types.d.ts +482 -0
- package/dist/backend/backend/src/types/orchestrator-state.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/orchestrator-state.types.js +83 -0
- package/dist/backend/backend/src/types/orchestrator-state.types.js.map +1 -0
- package/dist/backend/backend/src/types/quality-gate.types.d.ts +171 -0
- package/dist/backend/backend/src/types/quality-gate.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/quality-gate.types.js +42 -0
- package/dist/backend/backend/src/types/quality-gate.types.js.map +1 -0
- package/dist/backend/backend/src/types/role.types.d.ts +260 -0
- package/dist/backend/backend/src/types/role.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/role.types.js +238 -0
- package/dist/backend/backend/src/types/role.types.js.map +1 -0
- package/dist/backend/backend/src/types/scheduler.types.d.ts +237 -0
- package/dist/backend/backend/src/types/scheduler.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/scheduler.types.js +32 -0
- package/dist/backend/backend/src/types/scheduler.types.js.map +1 -0
- package/dist/backend/backend/src/types/settings.types.d.ts +178 -0
- package/dist/backend/backend/src/types/settings.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/settings.types.js +206 -0
- package/dist/backend/backend/src/types/settings.types.js.map +1 -0
- package/dist/backend/backend/src/types/skill.types.d.ts +515 -0
- package/dist/backend/backend/src/types/skill.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/skill.types.js +481 -0
- package/dist/backend/backend/src/types/skill.types.js.map +1 -0
- package/dist/backend/backend/src/types/slack.types.d.ts +329 -0
- package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/slack.types.js +66 -0
- package/dist/backend/backend/src/types/slack.types.js.map +1 -0
- package/dist/backend/backend/src/types/sop.types.d.ts +224 -0
- package/dist/backend/backend/src/types/sop.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/sop.types.js +85 -0
- package/dist/backend/backend/src/types/sop.types.js.map +1 -0
- package/dist/backend/backend/src/types/task-tracking.types.d.ts +91 -0
- package/dist/backend/backend/src/types/task-tracking.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/task-tracking.types.js +5 -0
- package/dist/backend/backend/src/types/task-tracking.types.js.map +1 -0
- package/dist/backend/backend/src/utils/async.utils.d.ts +68 -0
- package/dist/backend/backend/src/utils/async.utils.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/async.utils.js +80 -0
- package/dist/backend/backend/src/utils/async.utils.js.map +1 -0
- package/dist/backend/backend/src/utils/defaultPrompts.d.ts +5 -0
- package/dist/backend/backend/src/utils/defaultPrompts.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/defaultPrompts.js +285 -0
- package/dist/backend/backend/src/utils/defaultPrompts.js.map +1 -0
- package/dist/backend/backend/src/utils/file-io.utils.d.ts +102 -0
- package/dist/backend/backend/src/utils/file-io.utils.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/file-io.utils.js +214 -0
- package/dist/backend/backend/src/utils/file-io.utils.js.map +1 -0
- package/dist/backend/backend/src/utils/package-root.d.ts +15 -0
- package/dist/backend/backend/src/utils/package-root.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/package-root.js +38 -0
- package/dist/backend/backend/src/utils/package-root.js.map +1 -0
- package/dist/backend/backend/src/utils/process-recovery.d.ts +81 -0
- package/dist/backend/backend/src/utils/process-recovery.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/process-recovery.js +283 -0
- package/dist/backend/backend/src/utils/process-recovery.js.map +1 -0
- package/dist/backend/backend/src/utils/prompt-resolver.d.ts +79 -0
- package/dist/backend/backend/src/utils/prompt-resolver.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/prompt-resolver.js +132 -0
- package/dist/backend/backend/src/utils/prompt-resolver.js.map +1 -0
- package/dist/backend/backend/src/utils/resource-monitor.d.ts +177 -0
- package/dist/backend/backend/src/utils/resource-monitor.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/resource-monitor.js +404 -0
- package/dist/backend/backend/src/utils/resource-monitor.js.map +1 -0
- package/dist/backend/backend/src/utils/security.d.ts +194 -0
- package/dist/backend/backend/src/utils/security.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/security.js +533 -0
- package/dist/backend/backend/src/utils/security.js.map +1 -0
- package/dist/backend/backend/src/utils/terminal-output.utils.d.ts +54 -0
- package/dist/backend/backend/src/utils/terminal-output.utils.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/terminal-output.utils.js +97 -0
- package/dist/backend/backend/src/utils/terminal-output.utils.js.map +1 -0
- package/dist/backend/backend/src/websocket/chat.gateway.d.ts +127 -0
- package/dist/backend/backend/src/websocket/chat.gateway.d.ts.map +1 -0
- package/dist/backend/backend/src/websocket/chat.gateway.js +306 -0
- package/dist/backend/backend/src/websocket/chat.gateway.js.map +1 -0
- package/dist/backend/backend/src/websocket/file-watcher.gateway.d.ts +54 -0
- package/dist/backend/backend/src/websocket/file-watcher.gateway.d.ts.map +1 -0
- package/dist/backend/backend/src/websocket/file-watcher.gateway.js +202 -0
- package/dist/backend/backend/src/websocket/file-watcher.gateway.js.map +1 -0
- package/dist/backend/backend/src/websocket/terminal.gateway.d.ts +310 -0
- package/dist/backend/backend/src/websocket/terminal.gateway.d.ts.map +1 -0
- package/dist/backend/backend/src/websocket/terminal.gateway.js +980 -0
- package/dist/backend/backend/src/websocket/terminal.gateway.js.map +1 -0
- package/dist/backend/config/constants.d.ts +722 -0
- package/dist/backend/config/constants.d.ts.map +1 -0
- package/dist/backend/config/constants.js +752 -0
- package/dist/backend/config/constants.js.map +1 -0
- package/dist/backend/config/index.d.ts +344 -0
- package/dist/backend/config/index.d.ts.map +1 -0
- package/dist/backend/config/index.js +42 -0
- package/dist/backend/config/index.js.map +1 -0
- package/dist/backend/config/quality-gates/default-gates.d.ts +71 -0
- package/dist/backend/config/quality-gates/default-gates.d.ts.map +1 -0
- package/dist/backend/config/quality-gates/default-gates.js +168 -0
- package/dist/backend/config/quality-gates/default-gates.js.map +1 -0
- package/dist/cli/cli/src/commands/logs.d.ts +7 -0
- package/dist/cli/cli/src/commands/logs.d.ts.map +1 -0
- package/dist/cli/cli/src/commands/logs.js +198 -0
- package/dist/cli/cli/src/commands/logs.js.map +1 -0
- package/dist/cli/cli/src/commands/start.d.ts +8 -0
- package/dist/cli/cli/src/commands/start.d.ts.map +1 -0
- package/dist/cli/cli/src/commands/start.js +254 -0
- package/dist/cli/cli/src/commands/start.js.map +1 -0
- package/dist/cli/cli/src/commands/status.d.ts +6 -0
- package/dist/cli/cli/src/commands/status.d.ts.map +1 -0
- package/dist/cli/cli/src/commands/status.js +130 -0
- package/dist/cli/cli/src/commands/status.js.map +1 -0
- package/dist/cli/cli/src/commands/stop.d.ts +6 -0
- package/dist/cli/cli/src/commands/stop.d.ts.map +1 -0
- package/dist/cli/cli/src/commands/stop.js +140 -0
- package/dist/cli/cli/src/commands/stop.js.map +1 -0
- package/dist/cli/cli/src/commands/upgrade.d.ts +26 -0
- package/dist/cli/cli/src/commands/upgrade.d.ts.map +1 -0
- package/dist/cli/cli/src/commands/upgrade.js +54 -0
- package/dist/cli/cli/src/commands/upgrade.js.map +1 -0
- package/dist/cli/cli/src/constants.d.ts +106 -0
- package/dist/cli/cli/src/constants.d.ts.map +1 -0
- package/dist/cli/cli/src/constants.js +88 -0
- package/dist/cli/cli/src/constants.js.map +1 -0
- package/dist/cli/cli/src/index.d.ts +3 -0
- package/dist/cli/cli/src/index.d.ts.map +1 -0
- package/dist/cli/cli/src/index.js +60 -0
- package/dist/cli/cli/src/index.js.map +1 -0
- package/dist/cli/cli/src/utils/version-check.d.ts +66 -0
- package/dist/cli/cli/src/utils/version-check.d.ts.map +1 -0
- package/dist/cli/cli/src/utils/version-check.js +192 -0
- package/dist/cli/cli/src/utils/version-check.js.map +1 -0
- package/dist/cli/config/constants.d.ts +722 -0
- package/dist/cli/config/constants.d.ts.map +1 -0
- package/dist/cli/config/constants.js +752 -0
- package/dist/cli/config/constants.js.map +1 -0
- package/dist/cli/config/index.d.ts +344 -0
- package/dist/cli/config/index.d.ts.map +1 -0
- package/dist/cli/config/index.js +42 -0
- package/dist/cli/config/index.js.map +1 -0
- package/frontend/dist/assets/index-5ddf71c8.css +33 -0
- package/frontend/dist/assets/index-77b6a2a0.js +4919 -0
- package/frontend/dist/index.html +19 -0
- package/frontend/dist/logo/crewly-icon.svg +118 -0
- package/package.json +121 -0
|
@@ -0,0 +1,2863 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import { readFile, readdir, stat, mkdir, writeFile } from 'fs/promises';
|
|
4
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
5
|
+
import { createSessionCommandHelper, getSessionBackendSync, createSessionBackend, getSessionStatePersistence, } from '../session/index.js';
|
|
6
|
+
import { RuntimeServiceFactory } from './runtime-service.factory.js';
|
|
7
|
+
import { CREWLY_CONSTANTS, ENV_CONSTANTS, AGENT_TIMEOUTS, ORCHESTRATOR_ROLE, RUNTIME_TYPES, SESSION_COMMAND_DELAYS, EVENT_DELIVERY_CONSTANTS, TERMINAL_PATTERNS, GEMINI_SHELL_MODE_CONSTANTS, } from '../../constants.js';
|
|
8
|
+
import { WEB_CONSTANTS } from '../../../../config/constants.js';
|
|
9
|
+
import { delay } from '../../utils/async.utils.js';
|
|
10
|
+
import { getSettingsService } from '../settings/settings.service.js';
|
|
11
|
+
import { SessionMemoryService } from '../memory/session-memory.service.js';
|
|
12
|
+
import { RuntimeExitMonitorService } from './runtime-exit-monitor.service.js';
|
|
13
|
+
import { SubAgentMessageQueue } from '../messaging/sub-agent-message-queue.service.js';
|
|
14
|
+
import { AgentSuspendService } from './agent-suspend.service.js';
|
|
15
|
+
/**
|
|
16
|
+
* Service responsible for the complex, multi-step process of agent initialization and registration.
|
|
17
|
+
* Isolates the complex state management of agent startup with progressive escalation.
|
|
18
|
+
*
|
|
19
|
+
* Key capabilities:
|
|
20
|
+
* - Agent session creation and management
|
|
21
|
+
* - Runtime initialization and registration
|
|
22
|
+
* - Reliable message delivery to Claude Code with retry logic
|
|
23
|
+
* - Health checking and status management
|
|
24
|
+
*/
|
|
25
|
+
export class AgentRegistrationService {
|
|
26
|
+
logger;
|
|
27
|
+
_sessionHelper = null;
|
|
28
|
+
storageService;
|
|
29
|
+
projectRoot;
|
|
30
|
+
// Prompt file caching to eliminate file I/O contention during concurrent session creation
|
|
31
|
+
promptCache = new Map();
|
|
32
|
+
// AbortControllers for pending registration prompts (keyed by session name)
|
|
33
|
+
registrationAbortControllers = new Map();
|
|
34
|
+
// Background stuck-message detector timer
|
|
35
|
+
stuckMessageDetectorTimer = null;
|
|
36
|
+
// Track TUI sessions for the stuck-message detector (sessionName → runtimeType)
|
|
37
|
+
tuiSessionRegistry = new Map();
|
|
38
|
+
// Track recently-sent messages for background stuck-detection (all runtimes).
|
|
39
|
+
// Key: sessionName, Value: array of tracked message entries
|
|
40
|
+
sentMessageTracker = new Map();
|
|
41
|
+
// Terminal patterns are now centralized in TERMINAL_PATTERNS constant
|
|
42
|
+
// Keeping these as static getters for backwards compatibility within the class
|
|
43
|
+
static get CLAUDE_PROMPT_INDICATORS() {
|
|
44
|
+
return TERMINAL_PATTERNS.PROMPT_CHARS;
|
|
45
|
+
}
|
|
46
|
+
static get CLAUDE_PROMPT_STREAM_PATTERN() {
|
|
47
|
+
return TERMINAL_PATTERNS.PROMPT_STREAM;
|
|
48
|
+
}
|
|
49
|
+
static get CLAUDE_PROCESSING_INDICATORS() {
|
|
50
|
+
return TERMINAL_PATTERNS.PROCESSING_INDICATORS;
|
|
51
|
+
}
|
|
52
|
+
constructor(_legacyTmuxService, // Legacy parameter for backwards compatibility
|
|
53
|
+
projectRoot, storageService) {
|
|
54
|
+
this.logger = LoggerService.getInstance().createComponentLogger('AgentRegistrationService');
|
|
55
|
+
this.storageService = storageService;
|
|
56
|
+
this.projectRoot = projectRoot || this.findProjectRoot();
|
|
57
|
+
// Wire up the exit monitor to cancel pending registrations on runtime exit
|
|
58
|
+
RuntimeExitMonitorService.getInstance().setOnExitDetectedCallback((sessionName) => {
|
|
59
|
+
this.cancelPendingRegistration(sessionName);
|
|
60
|
+
this.unregisterTuiSession(sessionName);
|
|
61
|
+
// Preserve queued messages for suspended agents (they'll be delivered on rehydrate)
|
|
62
|
+
if (!AgentSuspendService.getInstance().isSuspended(sessionName)) {
|
|
63
|
+
SubAgentMessageQueue.getInstance().clear(sessionName);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Cancel a pending registration prompt for a session.
|
|
69
|
+
* Called by RuntimeExitMonitorService when a runtime exit is detected.
|
|
70
|
+
*
|
|
71
|
+
* @param sessionName - The session whose registration to cancel
|
|
72
|
+
*/
|
|
73
|
+
cancelPendingRegistration(sessionName) {
|
|
74
|
+
const controller = this.registrationAbortControllers.get(sessionName);
|
|
75
|
+
if (controller) {
|
|
76
|
+
controller.abort();
|
|
77
|
+
this.registrationAbortControllers.delete(sessionName);
|
|
78
|
+
this.logger.info('Cancelled pending registration', { sessionName });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get or create the session helper with lazy initialization
|
|
83
|
+
*/
|
|
84
|
+
async getSessionHelper() {
|
|
85
|
+
this.logger.debug('Getting session helper', {
|
|
86
|
+
hasExistingHelper: !!this._sessionHelper,
|
|
87
|
+
});
|
|
88
|
+
if (this._sessionHelper) {
|
|
89
|
+
return this._sessionHelper;
|
|
90
|
+
}
|
|
91
|
+
let backend = getSessionBackendSync();
|
|
92
|
+
this.logger.debug('Backend sync check', {
|
|
93
|
+
hasBackend: !!backend,
|
|
94
|
+
});
|
|
95
|
+
if (!backend) {
|
|
96
|
+
this.logger.info('Creating new PTY session backend');
|
|
97
|
+
backend = await createSessionBackend('pty');
|
|
98
|
+
this.logger.info('PTY session backend created', {
|
|
99
|
+
hasBackend: !!backend,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
this._sessionHelper = createSessionCommandHelper(backend);
|
|
103
|
+
this.logger.debug('Session helper created');
|
|
104
|
+
return this._sessionHelper;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get session helper synchronously (may throw if not initialized)
|
|
108
|
+
*/
|
|
109
|
+
getSessionHelperSync() {
|
|
110
|
+
if (!this._sessionHelper) {
|
|
111
|
+
const backend = getSessionBackendSync();
|
|
112
|
+
if (!backend) {
|
|
113
|
+
throw new Error('Session backend not initialized');
|
|
114
|
+
}
|
|
115
|
+
this._sessionHelper = createSessionCommandHelper(backend);
|
|
116
|
+
}
|
|
117
|
+
return this._sessionHelper;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Find the project root by looking for package.json
|
|
121
|
+
*/
|
|
122
|
+
findProjectRoot() {
|
|
123
|
+
// Simple fallback - could be improved to walk up directories
|
|
124
|
+
return process.cwd();
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Resolve runtime flags from the agent's effective skills.
|
|
128
|
+
* Reads role config and skill configs to collect flags compatible with the runtime.
|
|
129
|
+
*
|
|
130
|
+
* @param role - The agent's role name
|
|
131
|
+
* @param runtimeType - The agent's runtime type (e.g. 'claude-code')
|
|
132
|
+
* @param skillOverrides - Additional skills assigned to the member
|
|
133
|
+
* @param excludedRoleSkills - Skills excluded from the role's default set
|
|
134
|
+
* @returns Array of CLI flags to inject (e.g. ['--chrome'])
|
|
135
|
+
*/
|
|
136
|
+
async resolveRuntimeFlags(role, runtimeType, skillOverrides, excludedRoleSkills) {
|
|
137
|
+
const flags = new Set();
|
|
138
|
+
try {
|
|
139
|
+
// 1. Get role's assigned skills from config/roles/{role}/role.json
|
|
140
|
+
const roleName = role.toLowerCase().replace(/\s+/g, '-');
|
|
141
|
+
const rolePath = path.join(this.projectRoot, 'config', 'roles', roleName, 'role.json');
|
|
142
|
+
const roleContent = await readFile(rolePath, 'utf8');
|
|
143
|
+
const roleConfig = JSON.parse(roleContent);
|
|
144
|
+
const roleSkills = roleConfig.assignedSkills || [];
|
|
145
|
+
// 2. Apply skill overrides and exclusions
|
|
146
|
+
const effectiveSkills = new Set([...roleSkills, ...(skillOverrides || [])]);
|
|
147
|
+
for (const excluded of (excludedRoleSkills || [])) {
|
|
148
|
+
effectiveSkills.delete(excluded);
|
|
149
|
+
}
|
|
150
|
+
// 3. For each skill, read skill.json and collect flags matching runtime
|
|
151
|
+
for (const skillId of effectiveSkills) {
|
|
152
|
+
try {
|
|
153
|
+
const skillPath = path.join(this.projectRoot, 'config', 'skills', skillId, 'skill.json');
|
|
154
|
+
const skillContent = await readFile(skillPath, 'utf8');
|
|
155
|
+
const skillConfig = JSON.parse(skillContent);
|
|
156
|
+
if (skillConfig.runtime?.runtime === runtimeType && Array.isArray(skillConfig.runtime?.flags)) {
|
|
157
|
+
for (const flag of skillConfig.runtime.flags) {
|
|
158
|
+
flags.add(flag);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Skill config not found — skip silently
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (flags.size > 0) {
|
|
167
|
+
this.logger.info('Resolved runtime flags from skills', {
|
|
168
|
+
role, runtimeType, flags: Array.from(flags),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
this.logger.debug('Could not resolve runtime flags (no role config or read error)', {
|
|
174
|
+
role, runtimeType, error: error instanceof Error ? error.message : String(error),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return Array.from(flags);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Create a runtime service instance for the given runtime type.
|
|
181
|
+
* Centralizes RuntimeServiceFactory creation to reduce code duplication.
|
|
182
|
+
*/
|
|
183
|
+
createRuntimeService(runtimeType) {
|
|
184
|
+
return RuntimeServiceFactory.create(runtimeType, null, this.projectRoot);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get the check interval based on environment.
|
|
188
|
+
* Uses shorter intervals in test environment for faster tests.
|
|
189
|
+
*/
|
|
190
|
+
getCheckInterval() {
|
|
191
|
+
return process.env.NODE_ENV === 'test' ? 1000 : 2000;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Update agent status with safe error handling (non-blocking).
|
|
195
|
+
* Returns true if successful, false if failed.
|
|
196
|
+
*/
|
|
197
|
+
async updateAgentStatusSafe(sessionName, status, context) {
|
|
198
|
+
try {
|
|
199
|
+
await this.storageService.updateAgentStatus(sessionName, status);
|
|
200
|
+
this.logger.info(`Agent status updated to ${status}`, { sessionName, ...context });
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
this.logger.warn('Failed to update agent status (non-critical)', {
|
|
205
|
+
sessionName,
|
|
206
|
+
error: error instanceof Error ? error.message : String(error),
|
|
207
|
+
});
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get prompt file path for a specific role
|
|
213
|
+
* Uses the unified config/roles/{role}/prompt.md structure
|
|
214
|
+
*/
|
|
215
|
+
async getPromptFileForRole(role) {
|
|
216
|
+
// Normalize role name to directory name format
|
|
217
|
+
const roleName = role.toLowerCase().replace(/\s+/g, '-');
|
|
218
|
+
return path.join(process.cwd(), 'config', 'roles', roleName, 'prompt.md');
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Initialize agent with optimized 2-step escalation process
|
|
222
|
+
* Reduced from 4-step to 2-step with shorter timeouts for better concurrency
|
|
223
|
+
*/
|
|
224
|
+
async initializeAgentWithRegistration(sessionName, role, projectPath, timeout = AGENT_TIMEOUTS.REGULAR_AGENT_INITIALIZATION, memberId, runtimeType = RUNTIME_TYPES.CLAUDE_CODE, runtimeFlags) {
|
|
225
|
+
const startTime = Date.now();
|
|
226
|
+
this.logger.info('Starting optimized agent initialization with registration', {
|
|
227
|
+
sessionName,
|
|
228
|
+
role,
|
|
229
|
+
timeout,
|
|
230
|
+
runtimeType,
|
|
231
|
+
});
|
|
232
|
+
// PHASE 4: agentStatus is now set immediately in API endpoints (startTeam/startTeamMember)
|
|
233
|
+
// No longer need to set it here - focus only on session creation
|
|
234
|
+
this.logger.info('Starting agent session initialization', { sessionName, role });
|
|
235
|
+
// Clear detection cache to ensure fresh runtime detection
|
|
236
|
+
const runtimeService = this.createRuntimeService(runtimeType);
|
|
237
|
+
runtimeService.clearDetectionCache(sessionName);
|
|
238
|
+
// Skip Step 1 (direct registration) as it often fails in concurrent scenarios
|
|
239
|
+
// Go directly to Step 2: Cleanup + reinit (more reliable)
|
|
240
|
+
try {
|
|
241
|
+
this.logger.info('Step 1: Attempting cleanup and reinitialization', {
|
|
242
|
+
sessionName,
|
|
243
|
+
});
|
|
244
|
+
const step1Success = await this.tryCleanupAndReinit(sessionName, role, 40000, // 40 seconds for cleanup and reinit
|
|
245
|
+
projectPath, memberId, runtimeType, runtimeFlags);
|
|
246
|
+
if (step1Success) {
|
|
247
|
+
return {
|
|
248
|
+
success: true,
|
|
249
|
+
message: 'Agent registered successfully after cleanup and reinit',
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
this.logger.warn('Step 1 (cleanup + reinit) failed', {
|
|
255
|
+
sessionName,
|
|
256
|
+
error: error instanceof Error ? error.message : String(error),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
// Step 2: Full session recreation (30 seconds) - only if time remaining
|
|
260
|
+
if (Date.now() - startTime < timeout - 35000) {
|
|
261
|
+
try {
|
|
262
|
+
this.logger.info('Step 2: Attempting full session recreation', { sessionName });
|
|
263
|
+
const step2Success = await this.tryFullRecreation(sessionName, role, 30000, // Reduced from 45000 to 30000
|
|
264
|
+
projectPath, memberId, runtimeType, runtimeFlags);
|
|
265
|
+
if (step2Success) {
|
|
266
|
+
return {
|
|
267
|
+
success: true,
|
|
268
|
+
message: 'Agent registered successfully after full recreation',
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
this.logger.warn('Step 2 (full recreation) failed', {
|
|
274
|
+
sessionName,
|
|
275
|
+
error: error instanceof Error ? error.message : String(error),
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Give up after 2 steps
|
|
280
|
+
const errorMsg = `Failed to initialize agent after optimized escalation attempts (${Math.round((Date.now() - startTime) / 1000)}s)`;
|
|
281
|
+
this.logger.error(errorMsg, { sessionName, role });
|
|
282
|
+
return { success: false, error: errorMsg };
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Step 1: Try direct registration prompt
|
|
286
|
+
* Assumes Claude is already running and just needs a registration prompt
|
|
287
|
+
*/
|
|
288
|
+
async tryDirectRegistration(sessionName, role, timeout, memberId, runtimeType = RUNTIME_TYPES.CLAUDE_CODE) {
|
|
289
|
+
// Clear any existing input. Claude already performs Ctrl+C cleanup during
|
|
290
|
+
// runtime detection, so avoid sending additional Ctrl+C sequences.
|
|
291
|
+
// Gemini CLI: Skip cleanup — Ctrl+C at an empty prompt triggers /quit.
|
|
292
|
+
// Escape defocuses the TUI. Ctrl+U is ignored by the TUI.
|
|
293
|
+
const helper = await this.getSessionHelper();
|
|
294
|
+
// First check if runtime is running before sending the prompt
|
|
295
|
+
// runtimeService2: Separate instance for pre-registration runtime detection
|
|
296
|
+
// This instance is isolated from main runtimeService to avoid cache interference
|
|
297
|
+
const runtimeService2 = this.createRuntimeService(runtimeType);
|
|
298
|
+
const runtimeRunning = await runtimeService2.detectRuntimeWithCommand(sessionName);
|
|
299
|
+
if (!runtimeRunning) {
|
|
300
|
+
this.logger.debug('Runtime not detected in Step 1, skipping direct registration', {
|
|
301
|
+
sessionName,
|
|
302
|
+
runtimeType,
|
|
303
|
+
});
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
this.logger.debug('Runtime detected, sending registration prompt', {
|
|
307
|
+
sessionName,
|
|
308
|
+
runtimeType,
|
|
309
|
+
});
|
|
310
|
+
// Clear any pending slash command from detection. Claude detection already
|
|
311
|
+
// exits slash mode. Gemini CLI: skip — Ctrl+C at empty prompt exits CLI,
|
|
312
|
+
// Escape defocuses TUI, Ctrl+U is ignored. The prompt should be clean.
|
|
313
|
+
const prompt = await this.loadRegistrationPrompt(role, sessionName, memberId);
|
|
314
|
+
const promptDelivered = await this.sendPromptRobustly(sessionName, prompt, runtimeType);
|
|
315
|
+
if (!promptDelivered) {
|
|
316
|
+
this.logger.warn('Failed to deliver registration prompt', { sessionName, role });
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
return await this.waitForRegistration(sessionName, role, timeout);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Step 2: Cleanup with Ctrl+C and reinitialize
|
|
323
|
+
* Tries to reset the runtime session and start fresh
|
|
324
|
+
*/
|
|
325
|
+
async tryCleanupAndReinit(sessionName, role, timeout, projectPath, memberId, runtimeType = RUNTIME_TYPES.CLAUDE_CODE, runtimeFlags) {
|
|
326
|
+
// Clear Commandline
|
|
327
|
+
await (await this.getSessionHelper()).clearCurrentCommandLine(sessionName);
|
|
328
|
+
// Inject --resume flag if this was a previously running Claude Code session
|
|
329
|
+
const effectiveFlags = runtimeFlags ? [...runtimeFlags] : [];
|
|
330
|
+
if (runtimeType === RUNTIME_TYPES.CLAUDE_CODE) {
|
|
331
|
+
try {
|
|
332
|
+
const settings = await getSettingsService().getSettings();
|
|
333
|
+
if (settings.general.autoResumeOnRestart) {
|
|
334
|
+
const persistence = getSessionStatePersistence();
|
|
335
|
+
if (persistence.isRestoredSession(sessionName)) {
|
|
336
|
+
const storedSessionId = persistence.getSessionId(sessionName);
|
|
337
|
+
if (storedSessionId) {
|
|
338
|
+
effectiveFlags.push('--resume', storedSessionId);
|
|
339
|
+
this.logger.info('Injecting --resume flag for session restore', {
|
|
340
|
+
sessionName, sessionId: storedSessionId,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
this.logger.info('Auto-resume disabled by settings, skipping session resume', { sessionName });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch (resumeError) {
|
|
350
|
+
this.logger.debug('Could not resolve resume flag (non-fatal)', {
|
|
351
|
+
sessionName, error: resumeError instanceof Error ? resumeError.message : String(resumeError),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// Reinitialize runtime using the appropriate initialization script (always fresh start)
|
|
356
|
+
// runtimeService2: Fresh instance for runtime reinitialization after cleanup
|
|
357
|
+
// New instance ensures clean state without cached detection results
|
|
358
|
+
const runtimeService2 = this.createRuntimeService(runtimeType);
|
|
359
|
+
await runtimeService2.executeRuntimeInitScript(sessionName, projectPath, effectiveFlags);
|
|
360
|
+
// Wait for runtime to be ready (simplified detection)
|
|
361
|
+
// Use shorter check interval in test environment, and reasonable interval in production
|
|
362
|
+
const checkInterval = process.env.NODE_ENV === 'test' ? 1000 : 2000; // Check every 1-2 seconds
|
|
363
|
+
const isReady = await runtimeService2.waitForRuntimeReady(sessionName, 30000, checkInterval); // 30s timeout
|
|
364
|
+
if (!isReady) {
|
|
365
|
+
throw new Error(`Failed to reinitialize ${runtimeType} within timeout`);
|
|
366
|
+
}
|
|
367
|
+
// Start runtime exit monitoring IMMEDIATELY after runtime is ready.
|
|
368
|
+
// Must be before postInitialize and sendRegistrationPromptAsync so exits
|
|
369
|
+
// during those phases are detected and the abort signal fires in time.
|
|
370
|
+
RuntimeExitMonitorService.getInstance().startMonitoring(sessionName, runtimeType, role);
|
|
371
|
+
// Run post-initialization hook (e.g., Gemini CLI directory allowlist)
|
|
372
|
+
try {
|
|
373
|
+
await runtimeService2.postInitialize(sessionName, projectPath);
|
|
374
|
+
// Drain stale terminal escape sequences (e.g. DA1 [?1;2c) that may have
|
|
375
|
+
// arrived during postInitialize commands, so they don't leak into the prompt input
|
|
376
|
+
await delay(500);
|
|
377
|
+
// Clear any pending input after post-initialization.
|
|
378
|
+
// Claude Code: Ctrl+C + Ctrl+U (clearCurrentCommandLine) — standard cleanup.
|
|
379
|
+
// Gemini CLI: Skip cleanup entirely — the TUI just started with a clean
|
|
380
|
+
// prompt. Ctrl+C at an empty Gemini CLI prompt triggers /quit and exits
|
|
381
|
+
// the CLI. Escape defocuses the TUI. Ctrl+U is ignored. The delay(500)
|
|
382
|
+
// above is sufficient to drain stale escape sequences.
|
|
383
|
+
if (runtimeType === RUNTIME_TYPES.CLAUDE_CODE) {
|
|
384
|
+
await (await this.getSessionHelper()).clearCurrentCommandLine(sessionName);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
catch (postInitError) {
|
|
388
|
+
this.logger.warn('Post-initialization hook failed (non-fatal)', {
|
|
389
|
+
sessionName,
|
|
390
|
+
runtimeType,
|
|
391
|
+
error: postInitError instanceof Error ? postInitError.message : String(postInitError),
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
// For PTY sessions, once runtime is detected as ready, consider initialization successful
|
|
395
|
+
// MCP registration will happen async when the agent processes its first prompt
|
|
396
|
+
this.logger.info('Runtime detected as ready, session initialization successful', {
|
|
397
|
+
sessionName,
|
|
398
|
+
role,
|
|
399
|
+
runtimeType,
|
|
400
|
+
});
|
|
401
|
+
// Background: detect and store session ID for resume-on-restart
|
|
402
|
+
if (runtimeType === RUNTIME_TYPES.CLAUDE_CODE) {
|
|
403
|
+
const detectPath = projectPath || process.cwd();
|
|
404
|
+
setTimeout(() => {
|
|
405
|
+
this.detectAndStoreSessionId(sessionName, detectPath).catch(() => { });
|
|
406
|
+
}, 10000);
|
|
407
|
+
}
|
|
408
|
+
// Send the registration prompt in background (don't block on it)
|
|
409
|
+
this.sendRegistrationPromptAsync(sessionName, role, memberId, runtimeType).catch((err) => {
|
|
410
|
+
this.logger.warn('Background registration prompt failed (non-blocking)', {
|
|
411
|
+
sessionName,
|
|
412
|
+
error: err instanceof Error ? err.message : String(err),
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
// Update agent status to 'started' since the runtime is running
|
|
416
|
+
// The agent will become 'active' only after it registers via the API endpoint
|
|
417
|
+
try {
|
|
418
|
+
await this.storageService.updateAgentStatus(sessionName, CREWLY_CONSTANTS.AGENT_STATUSES.STARTED);
|
|
419
|
+
this.logger.info('Agent status updated to started (runtime running, awaiting registration)', { sessionName, role });
|
|
420
|
+
}
|
|
421
|
+
catch (statusError) {
|
|
422
|
+
this.logger.warn('Failed to update agent status (non-critical)', {
|
|
423
|
+
sessionName,
|
|
424
|
+
error: statusError instanceof Error ? statusError.message : String(statusError),
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Detect and store the Claude Code session ID by scanning the filesystem.
|
|
431
|
+
* Claude Code stores conversations as <UUID>.jsonl files in
|
|
432
|
+
* ~/.claude/projects/<path-slug>/ where path-slug is the absolute
|
|
433
|
+
* project path with / replaced by -.
|
|
434
|
+
*
|
|
435
|
+
* Runs in background after agent init to capture the session ID
|
|
436
|
+
* for resume-on-restart support.
|
|
437
|
+
*
|
|
438
|
+
* @param sessionName - PTY session name
|
|
439
|
+
* @param projectPath - Absolute path to the agent's project directory
|
|
440
|
+
*/
|
|
441
|
+
async detectAndStoreSessionId(sessionName, projectPath) {
|
|
442
|
+
try {
|
|
443
|
+
const slug = projectPath.replace(/\//g, '-');
|
|
444
|
+
const claudeProjectDir = path.join(os.homedir(), '.claude', 'projects', slug);
|
|
445
|
+
const files = await readdir(claudeProjectDir);
|
|
446
|
+
const jsonlFiles = files.filter(f => f.endsWith('.jsonl'));
|
|
447
|
+
if (jsonlFiles.length === 0)
|
|
448
|
+
return;
|
|
449
|
+
// Find most recent .jsonl by mtime
|
|
450
|
+
let latestFile = '';
|
|
451
|
+
let latestMtime = 0;
|
|
452
|
+
for (const file of jsonlFiles) {
|
|
453
|
+
const fileStat = await stat(path.join(claudeProjectDir, file));
|
|
454
|
+
if (fileStat.mtimeMs > latestMtime) {
|
|
455
|
+
latestMtime = fileStat.mtimeMs;
|
|
456
|
+
latestFile = file;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (!latestFile)
|
|
460
|
+
return;
|
|
461
|
+
// Extract UUID from filename (remove .jsonl extension)
|
|
462
|
+
const sessionId = latestFile.replace('.jsonl', '');
|
|
463
|
+
const persistence = getSessionStatePersistence();
|
|
464
|
+
persistence.updateSessionId(sessionName, sessionId);
|
|
465
|
+
this.logger.info('Detected and stored Claude session ID from filesystem', {
|
|
466
|
+
sessionName,
|
|
467
|
+
sessionId,
|
|
468
|
+
claudeProjectDir,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
this.logger.debug('Could not detect Claude session ID from filesystem (non-fatal)', {
|
|
473
|
+
sessionName,
|
|
474
|
+
error: error instanceof Error ? error.message : String(error),
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Send registration prompt asynchronously (non-blocking).
|
|
480
|
+
* Uses an AbortController so the operation can be cancelled if the
|
|
481
|
+
* runtime exits before registration completes.
|
|
482
|
+
*/
|
|
483
|
+
async sendRegistrationPromptAsync(sessionName, role, memberId, runtimeType = RUNTIME_TYPES.CLAUDE_CODE) {
|
|
484
|
+
// Create AbortController for this registration
|
|
485
|
+
const controller = new AbortController();
|
|
486
|
+
this.registrationAbortControllers.set(sessionName, controller);
|
|
487
|
+
try {
|
|
488
|
+
if (controller.signal.aborted)
|
|
489
|
+
return;
|
|
490
|
+
const prompt = await this.loadRegistrationPrompt(role, sessionName, memberId);
|
|
491
|
+
if (controller.signal.aborted)
|
|
492
|
+
return;
|
|
493
|
+
await this.sendPromptRobustly(sessionName, prompt, runtimeType, controller.signal);
|
|
494
|
+
this.logger.debug('Registration prompt sent asynchronously', { sessionName, role });
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
if (controller.signal.aborted) {
|
|
498
|
+
this.logger.info('Registration prompt cancelled (runtime exited)', { sessionName });
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
this.logger.warn('Failed to send registration prompt asynchronously', {
|
|
502
|
+
sessionName,
|
|
503
|
+
error: error instanceof Error ? error.message : String(error),
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
finally {
|
|
507
|
+
this.registrationAbortControllers.delete(sessionName);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Step 3: Kill session and recreate completely
|
|
512
|
+
* Most aggressive approach - completely recreates the session from scratch
|
|
513
|
+
*/
|
|
514
|
+
async tryFullRecreation(sessionName, role, timeout, projectPath, memberId, runtimeType = RUNTIME_TYPES.CLAUDE_CODE, runtimeFlags) {
|
|
515
|
+
// Kill existing session
|
|
516
|
+
await (await this.getSessionHelper()).killSession(sessionName);
|
|
517
|
+
// Wait for cleanup
|
|
518
|
+
await delay(1000);
|
|
519
|
+
// Inject --resume flag if this was a previously running Claude Code session
|
|
520
|
+
const effectiveFlags = runtimeFlags ? [...runtimeFlags] : [];
|
|
521
|
+
if (runtimeType === RUNTIME_TYPES.CLAUDE_CODE) {
|
|
522
|
+
try {
|
|
523
|
+
const settings = await getSettingsService().getSettings();
|
|
524
|
+
if (settings.general.autoResumeOnRestart) {
|
|
525
|
+
const persistence = getSessionStatePersistence();
|
|
526
|
+
if (persistence.isRestoredSession(sessionName)) {
|
|
527
|
+
const storedSessionId = persistence.getSessionId(sessionName);
|
|
528
|
+
if (storedSessionId) {
|
|
529
|
+
effectiveFlags.push('--resume', storedSessionId);
|
|
530
|
+
this.logger.info('Injecting --resume flag for session restore (full recreation)', {
|
|
531
|
+
sessionName, sessionId: storedSessionId,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
this.logger.info('Auto-resume disabled by settings, skipping session resume after recreation', { sessionName });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
catch (resumeError) {
|
|
541
|
+
this.logger.debug('Could not resolve resume flag (non-fatal)', {
|
|
542
|
+
sessionName, error: resumeError instanceof Error ? resumeError.message : String(resumeError),
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// Recreate session based on role
|
|
547
|
+
if (role === ORCHESTRATOR_ROLE) {
|
|
548
|
+
await this.createOrchestratorSession({
|
|
549
|
+
sessionName,
|
|
550
|
+
projectPath: projectPath || process.cwd(),
|
|
551
|
+
});
|
|
552
|
+
// Initialize runtime for orchestrator using script (always fresh start)
|
|
553
|
+
const runtimeService = this.createRuntimeService(runtimeType);
|
|
554
|
+
await runtimeService.executeRuntimeInitScript(sessionName, process.cwd(), effectiveFlags);
|
|
555
|
+
// Wait for runtime to be ready
|
|
556
|
+
const checkInterval = this.getCheckInterval();
|
|
557
|
+
// runtimeService3: Separate instance for orchestrator ready-state detection
|
|
558
|
+
// Isolated from runtimeService to prevent interference between init and ready checks
|
|
559
|
+
const runtimeService3 = this.createRuntimeService(runtimeType);
|
|
560
|
+
const isReady = await runtimeService3.waitForRuntimeReady(sessionName, 45000, checkInterval);
|
|
561
|
+
if (!isReady) {
|
|
562
|
+
throw new Error(`Failed to initialize ${runtimeType} in recreated orchestrator session within timeout`);
|
|
563
|
+
}
|
|
564
|
+
// Start runtime exit monitoring immediately after runtime is ready
|
|
565
|
+
RuntimeExitMonitorService.getInstance().startMonitoring(sessionName, runtimeType, role);
|
|
566
|
+
// Additional verification: Use runtime detection to confirm runtime is responding
|
|
567
|
+
// Wait a bit longer for runtime to fully load after showing welcome message
|
|
568
|
+
this.logger.debug('Runtime ready detected for orchestrator, waiting for full startup before verification', { sessionName, runtimeType });
|
|
569
|
+
await delay(5000);
|
|
570
|
+
this.logger.debug('Verifying orchestrator runtime responsiveness', {
|
|
571
|
+
sessionName,
|
|
572
|
+
runtimeType,
|
|
573
|
+
});
|
|
574
|
+
// runtimeService4: Final verification instance for orchestrator responsiveness
|
|
575
|
+
// Clean instance for post-initialization responsiveness testing
|
|
576
|
+
const runtimeService4 = this.createRuntimeService(runtimeType);
|
|
577
|
+
const runtimeResponding = await runtimeService4.detectRuntimeWithCommand(sessionName);
|
|
578
|
+
if (!runtimeResponding) {
|
|
579
|
+
throw new Error(`${runtimeType} not responding to commands after orchestrator recreation`);
|
|
580
|
+
}
|
|
581
|
+
this.logger.debug('Runtime confirmed ready for orchestrator in Step 3', { sessionName, runtimeType });
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
// For other roles, create basic session and initialize Claude (always fresh start)
|
|
585
|
+
await (await this.getSessionHelper()).createSession(sessionName, projectPath || process.cwd());
|
|
586
|
+
const runtimeService = this.createRuntimeService(runtimeType);
|
|
587
|
+
await runtimeService.executeRuntimeInitScript(sessionName, projectPath, effectiveFlags);
|
|
588
|
+
// Wait for runtime to be ready (simplified detection)
|
|
589
|
+
const checkInterval = this.getCheckInterval();
|
|
590
|
+
const isReady = await this.createRuntimeService(runtimeType)
|
|
591
|
+
.waitForRuntimeReady(sessionName, 25000, checkInterval); // 25s timeout
|
|
592
|
+
if (!isReady) {
|
|
593
|
+
throw new Error(`Failed to initialize ${runtimeType} in recreated session within timeout`);
|
|
594
|
+
}
|
|
595
|
+
// Start runtime exit monitoring immediately after runtime is ready
|
|
596
|
+
RuntimeExitMonitorService.getInstance().startMonitoring(sessionName, runtimeType, role);
|
|
597
|
+
}
|
|
598
|
+
// Run post-initialization hook (e.g., Gemini CLI directory allowlist)
|
|
599
|
+
try {
|
|
600
|
+
const postInitService = this.createRuntimeService(runtimeType);
|
|
601
|
+
await postInitService.postInitialize(sessionName, projectPath);
|
|
602
|
+
// Drain stale terminal escape sequences (e.g. DA1 [?1;2c) that may have
|
|
603
|
+
// arrived during postInitialize commands, so they don't leak into the prompt input
|
|
604
|
+
await delay(500);
|
|
605
|
+
// Claude Code: Ctrl+C + Ctrl+U to clear any stale input.
|
|
606
|
+
// Gemini CLI (Ink TUI): Do NOT send any cleanup keystrokes.
|
|
607
|
+
// Escape defocuses the Ink TUI input permanently. Ctrl+C at empty
|
|
608
|
+
// prompt triggers /quit. Ctrl+U is ignored. The delay above is
|
|
609
|
+
// sufficient to drain stale escape sequences.
|
|
610
|
+
if (runtimeType === RUNTIME_TYPES.CLAUDE_CODE) {
|
|
611
|
+
await (await this.getSessionHelper()).clearCurrentCommandLine(sessionName);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
catch (postInitError) {
|
|
615
|
+
this.logger.warn('Post-initialization hook failed after recreation (non-fatal)', {
|
|
616
|
+
sessionName,
|
|
617
|
+
runtimeType,
|
|
618
|
+
error: postInitError instanceof Error ? postInitError.message : String(postInitError),
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
// For PTY sessions, once runtime is detected as ready, consider initialization successful
|
|
622
|
+
this.logger.info('Runtime detected as ready after full recreation, session initialization successful', {
|
|
623
|
+
sessionName,
|
|
624
|
+
role,
|
|
625
|
+
runtimeType,
|
|
626
|
+
});
|
|
627
|
+
// Background: detect and store session ID for resume-on-restart
|
|
628
|
+
if (runtimeType === RUNTIME_TYPES.CLAUDE_CODE) {
|
|
629
|
+
const detectPath = projectPath || process.cwd();
|
|
630
|
+
setTimeout(() => {
|
|
631
|
+
this.detectAndStoreSessionId(sessionName, detectPath).catch(() => { });
|
|
632
|
+
}, 10000);
|
|
633
|
+
}
|
|
634
|
+
// Send the registration prompt in background (don't block on it)
|
|
635
|
+
this.sendRegistrationPromptAsync(sessionName, role, memberId, runtimeType).catch((err) => {
|
|
636
|
+
this.logger.warn('Background registration prompt failed after recreation (non-blocking)', {
|
|
637
|
+
sessionName,
|
|
638
|
+
error: err instanceof Error ? err.message : String(err),
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
// Update agent status to 'started' since the runtime is running
|
|
642
|
+
// The agent will become 'active' only after it registers via the API endpoint
|
|
643
|
+
try {
|
|
644
|
+
await this.storageService.updateAgentStatus(sessionName, CREWLY_CONSTANTS.AGENT_STATUSES.STARTED);
|
|
645
|
+
this.logger.info('Agent status updated to started after recreation (awaiting registration)', { sessionName, role });
|
|
646
|
+
}
|
|
647
|
+
catch (statusError) {
|
|
648
|
+
this.logger.warn('Failed to update agent status after recreation (non-critical)', {
|
|
649
|
+
sessionName,
|
|
650
|
+
error: statusError instanceof Error ? statusError.message : String(statusError),
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Load registration prompt from config files (with caching to prevent file I/O contention)
|
|
657
|
+
*/
|
|
658
|
+
async loadRegistrationPrompt(role, sessionName, memberId) {
|
|
659
|
+
try {
|
|
660
|
+
// Cache key is role only - the template file is the same regardless of memberId
|
|
661
|
+
const cacheKey = role;
|
|
662
|
+
// Check cache first to avoid file I/O during concurrent operations
|
|
663
|
+
if (!this.promptCache.has(cacheKey)) {
|
|
664
|
+
this.logger.debug('Loading prompt template from file', { role, cacheKey });
|
|
665
|
+
// Get the prompt file path from team roles configuration
|
|
666
|
+
const promptPath = await this.getPromptFileForRole(role);
|
|
667
|
+
const promptTemplate = await readFile(promptPath, 'utf8');
|
|
668
|
+
this.promptCache.set(cacheKey, promptTemplate);
|
|
669
|
+
this.logger.debug('Prompt template cached', { role, cacheKey, promptPath });
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
this.logger.debug('Using cached prompt template', { role, cacheKey });
|
|
673
|
+
}
|
|
674
|
+
// Get cached template and apply variable replacements
|
|
675
|
+
let prompt = this.promptCache.get(cacheKey);
|
|
676
|
+
// Replace all template placeholders
|
|
677
|
+
prompt = prompt.replace(/\{\{SESSION_ID\}\}/g, sessionName);
|
|
678
|
+
prompt = prompt.replace(/\{\{SESSION_NAME\}\}/g, sessionName);
|
|
679
|
+
prompt = prompt.replace(/\{\{ROLE\}\}/g, role);
|
|
680
|
+
if (memberId) {
|
|
681
|
+
prompt = prompt.replace(/\{\{MEMBER_ID\}\}/g, memberId);
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
// For orchestrator or cases without member ID, remove the memberId parameter
|
|
685
|
+
prompt = prompt.replace(/,\s*"memberId":\s*"\{\{MEMBER_ID\}\}"/g, '');
|
|
686
|
+
}
|
|
687
|
+
// Look up project path for team members
|
|
688
|
+
let projectPath = process.cwd();
|
|
689
|
+
try {
|
|
690
|
+
const teams = await this.storageService.getTeams();
|
|
691
|
+
for (const team of teams) {
|
|
692
|
+
const member = team.members?.find((m) => m.sessionName === sessionName);
|
|
693
|
+
if (member && team.projectIds[0]) {
|
|
694
|
+
const projects = await this.storageService.getProjects();
|
|
695
|
+
const project = projects.find((p) => p.id === team.projectIds[0]);
|
|
696
|
+
if (project?.path) {
|
|
697
|
+
projectPath = project.path;
|
|
698
|
+
}
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
catch {
|
|
704
|
+
// Non-critical - use default project path
|
|
705
|
+
}
|
|
706
|
+
// Replace project path placeholder (must happen after project path lookup above)
|
|
707
|
+
prompt = prompt.replace(/\{\{PROJECT_PATH\}\}/g, projectPath);
|
|
708
|
+
// Replace agent skills path placeholder
|
|
709
|
+
const agentSkillsPath = path.join(this.projectRoot, 'config', 'skills', 'agent');
|
|
710
|
+
prompt = prompt.replace(/\{\{AGENT_SKILLS_PATH\}\}/g, agentSkillsPath);
|
|
711
|
+
// Generate and inject startup briefing from session memory
|
|
712
|
+
try {
|
|
713
|
+
const sessionMemoryService = SessionMemoryService.getInstance();
|
|
714
|
+
await sessionMemoryService.onSessionStart(sessionName, role, projectPath);
|
|
715
|
+
const briefing = await sessionMemoryService.generateStartupBriefing(sessionName, role, projectPath);
|
|
716
|
+
const briefingMd = sessionMemoryService.formatBriefingAsMarkdown(briefing);
|
|
717
|
+
if (briefingMd && briefingMd.length > 30) {
|
|
718
|
+
prompt += `\n\n---\n\n${briefingMd}`;
|
|
719
|
+
this.logger.info('Startup briefing injected into prompt', { sessionName, role, briefingLength: briefingMd.length });
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
catch (briefingError) {
|
|
723
|
+
this.logger.warn('Failed to generate startup briefing (non-critical)', {
|
|
724
|
+
sessionName,
|
|
725
|
+
error: briefingError instanceof Error ? briefingError.message : String(briefingError),
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
// Append identity section so the agent knows its session name and project path
|
|
729
|
+
prompt += `\n\n---\n\n## Your Identity\n- **Session Name:** ${sessionName}\n- **Project Path:** ${projectPath}`;
|
|
730
|
+
if (memberId) {
|
|
731
|
+
prompt += `\n- **Member ID:** ${memberId}`;
|
|
732
|
+
}
|
|
733
|
+
return prompt;
|
|
734
|
+
}
|
|
735
|
+
catch (error) {
|
|
736
|
+
// Fallback to inline prompt if file doesn't exist
|
|
737
|
+
const attemptedPath = await this.getPromptFileForRole(role);
|
|
738
|
+
this.logger.error('Could not load prompt from config, using fallback', {
|
|
739
|
+
role,
|
|
740
|
+
promptPath: attemptedPath,
|
|
741
|
+
error: error instanceof Error ? error.message : String(error),
|
|
742
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
743
|
+
});
|
|
744
|
+
const memberIdJson = memberId ? `,"teamMemberId":"${memberId}"` : '';
|
|
745
|
+
const skillsPath = path.join(this.projectRoot, 'config', 'skills', 'agent');
|
|
746
|
+
return `Hey! I need your help with some tasks on this project.
|
|
747
|
+
|
|
748
|
+
## Quick context about this setup
|
|
749
|
+
|
|
750
|
+
This project uses Crewly for team coordination. You have bash skills available at \`${skillsPath}/\` that communicate with the Crewly backend running locally. Read the skills catalog at \`~/.crewly/skills/AGENT_SKILLS_CATALOG.md\` for a full reference of available skills.
|
|
751
|
+
|
|
752
|
+
## First thing - please check in
|
|
753
|
+
|
|
754
|
+
Please run the register-self skill to let the team dashboard know you're available:
|
|
755
|
+
\`\`\`bash
|
|
756
|
+
bash ${skillsPath}/register-self/execute.sh '{"role":"${role}","sessionName":"${sessionName}"${memberIdJson}}'
|
|
757
|
+
\`\`\`
|
|
758
|
+
All it does is update a local status flag so the web UI shows you as online - nothing more.
|
|
759
|
+
|
|
760
|
+
After checking in, just say "Ready for tasks" and wait for me to send you work.`;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Wait for agent registration to complete
|
|
765
|
+
*/
|
|
766
|
+
async waitForRegistration(sessionName, role, timeout) {
|
|
767
|
+
const startTime = Date.now();
|
|
768
|
+
const checkInterval = 5000; // Check every 5 seconds to prevent overlapping with `/` detection
|
|
769
|
+
while (Date.now() - startTime < timeout) {
|
|
770
|
+
try {
|
|
771
|
+
if (await this.checkAgentRegistration(sessionName, role)) {
|
|
772
|
+
this.logger.info('Agent registration confirmed', { sessionName, role });
|
|
773
|
+
return true;
|
|
774
|
+
}
|
|
775
|
+
await delay(checkInterval);
|
|
776
|
+
}
|
|
777
|
+
catch (error) {
|
|
778
|
+
this.logger.warn('Error checking registration', {
|
|
779
|
+
sessionName,
|
|
780
|
+
role,
|
|
781
|
+
error: error instanceof Error ? error.message : String(error),
|
|
782
|
+
});
|
|
783
|
+
await delay(checkInterval);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
this.logger.warn('Timeout waiting for agent registration', { sessionName, role, timeout });
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Check if agent is properly registered
|
|
791
|
+
*/
|
|
792
|
+
async checkAgentRegistration(sessionName, role) {
|
|
793
|
+
try {
|
|
794
|
+
if (role === ORCHESTRATOR_ROLE) {
|
|
795
|
+
// For orchestrator, check agentStatus is active
|
|
796
|
+
const orchestratorStatus = await this.storageService.getOrchestratorStatus();
|
|
797
|
+
return orchestratorStatus?.agentStatus === CREWLY_CONSTANTS.AGENT_STATUSES.ACTIVE;
|
|
798
|
+
}
|
|
799
|
+
// For team members, check teams data
|
|
800
|
+
const teams = await this.storageService.getTeams();
|
|
801
|
+
// Find team member with matching sessionName and check agentStatus
|
|
802
|
+
for (const team of teams) {
|
|
803
|
+
if (team.members) {
|
|
804
|
+
for (const member of team.members) {
|
|
805
|
+
if (member.sessionName === sessionName && member.role === role) {
|
|
806
|
+
return member.agentStatus === CREWLY_CONSTANTS.AGENT_STATUSES.ACTIVE;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
catch (error) {
|
|
814
|
+
this.logger.debug('Error checking agent registration', {
|
|
815
|
+
sessionName,
|
|
816
|
+
role,
|
|
817
|
+
error: error instanceof Error ? error.message : String(error),
|
|
818
|
+
});
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Fast registration with verification and retry mechanism
|
|
824
|
+
* Sends system prompt to runtime which triggers MCP registration via teams.json
|
|
825
|
+
* @param skipInitialCleanup If true, skips Ctrl+C on first attempt (when runtime was just initialized)
|
|
826
|
+
*/
|
|
827
|
+
async attemptRegistrationWithVerification(sessionName, role, timeout, memberId, maxRetries = 3, skipInitialCleanup = false, runtimeType = RUNTIME_TYPES.CLAUDE_CODE) {
|
|
828
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
829
|
+
this.logger.info('Attempting system prompt registration with MCP flow', {
|
|
830
|
+
sessionName,
|
|
831
|
+
role,
|
|
832
|
+
runtimeType,
|
|
833
|
+
attempt,
|
|
834
|
+
maxRetries,
|
|
835
|
+
});
|
|
836
|
+
try {
|
|
837
|
+
// Step 1: Send Ctrl+C to clear any pending commands (skip on first attempt if Claude was just initialized)
|
|
838
|
+
if (!skipInitialCleanup || attempt > 1) {
|
|
839
|
+
await (await this.getSessionHelper()).clearCurrentCommandLine(sessionName);
|
|
840
|
+
await delay(500);
|
|
841
|
+
this.logger.debug('Sent Ctrl+C to clear terminal state', {
|
|
842
|
+
sessionName,
|
|
843
|
+
attempt,
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
else {
|
|
847
|
+
this.logger.debug('Skipping Ctrl+C on first attempt (Claude was just initialized)', { sessionName, attempt });
|
|
848
|
+
}
|
|
849
|
+
// Step 2: Verify runtime is running (skip detection if runtime was just initialized and verified)
|
|
850
|
+
let runtimeRunning = true; // Assume true if we skip detection
|
|
851
|
+
if (!skipInitialCleanup || attempt > 1) {
|
|
852
|
+
// Only do runtime detection on retries or when runtime wasn't just initialized
|
|
853
|
+
const forceRefresh = attempt > 1; // Force refresh on retry attempts
|
|
854
|
+
const runtimeService5 = this.createRuntimeService(runtimeType);
|
|
855
|
+
runtimeRunning = await runtimeService5.detectRuntimeWithCommand(sessionName, forceRefresh);
|
|
856
|
+
if (!runtimeRunning) {
|
|
857
|
+
this.logger.warn('Runtime not detected, cannot send system prompt', {
|
|
858
|
+
sessionName,
|
|
859
|
+
runtimeType,
|
|
860
|
+
attempt,
|
|
861
|
+
forceRefresh,
|
|
862
|
+
});
|
|
863
|
+
// Clear detection cache before continuing to retry
|
|
864
|
+
const runtimeService6 = this.createRuntimeService(runtimeType);
|
|
865
|
+
runtimeService6.clearDetectionCache(sessionName);
|
|
866
|
+
// Add longer delay between failed detection attempts
|
|
867
|
+
if (attempt < maxRetries) {
|
|
868
|
+
await delay(2000);
|
|
869
|
+
}
|
|
870
|
+
continue; // Try again
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
this.logger.debug('Skipping runtime detection (runtime was just initialized and verified)', {
|
|
875
|
+
sessionName,
|
|
876
|
+
runtimeType,
|
|
877
|
+
attempt,
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
// Step 3: Send system prompt with robust delivery mechanism
|
|
881
|
+
const prompt = await this.loadRegistrationPrompt(role, sessionName, memberId);
|
|
882
|
+
const promptDelivered = await this.sendPromptRobustly(sessionName, prompt, runtimeType);
|
|
883
|
+
if (!promptDelivered) {
|
|
884
|
+
this.logger.warn('Failed to deliver system prompt reliably', {
|
|
885
|
+
sessionName,
|
|
886
|
+
attempt,
|
|
887
|
+
promptLength: prompt.length,
|
|
888
|
+
});
|
|
889
|
+
continue; // Try again
|
|
890
|
+
}
|
|
891
|
+
this.logger.debug('System prompt delivered successfully', {
|
|
892
|
+
sessionName,
|
|
893
|
+
promptLength: prompt.length,
|
|
894
|
+
});
|
|
895
|
+
this.logger.debug('Terminal activity detected, waiting for MCP registration', {
|
|
896
|
+
sessionName,
|
|
897
|
+
});
|
|
898
|
+
// Step 5: Wait for registration API call to update teams.json (agentStatus: activating -> active)
|
|
899
|
+
const registrationTimeout = Math.min(timeout, 25000); // Max 25s per attempt
|
|
900
|
+
const registered = await this.waitForRegistrationFast(sessionName, role, registrationTimeout);
|
|
901
|
+
if (registered) {
|
|
902
|
+
this.logger.info('MCP registration successful (teams.json updated)', {
|
|
903
|
+
sessionName,
|
|
904
|
+
role,
|
|
905
|
+
attempt,
|
|
906
|
+
});
|
|
907
|
+
return true;
|
|
908
|
+
}
|
|
909
|
+
this.logger.warn('MCP registration timeout, retrying', {
|
|
910
|
+
sessionName,
|
|
911
|
+
role,
|
|
912
|
+
attempt,
|
|
913
|
+
nextAttempt: attempt < maxRetries,
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
catch (error) {
|
|
917
|
+
this.logger.error('System prompt registration attempt failed', {
|
|
918
|
+
sessionName,
|
|
919
|
+
role,
|
|
920
|
+
attempt,
|
|
921
|
+
error: error instanceof Error ? error.message : String(error),
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
// Short delay before retry
|
|
925
|
+
if (attempt < maxRetries) {
|
|
926
|
+
await delay(1000);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
this.logger.error('All runtime registration attempts failed', {
|
|
930
|
+
sessionName,
|
|
931
|
+
role,
|
|
932
|
+
runtimeType,
|
|
933
|
+
maxRetries,
|
|
934
|
+
});
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Fast registration polling with shorter intervals
|
|
939
|
+
*/
|
|
940
|
+
async waitForRegistrationFast(sessionName, role, timeout) {
|
|
941
|
+
const startTime = Date.now();
|
|
942
|
+
const fastCheckInterval = 2000; // Check every 2 seconds (faster than original 5s)
|
|
943
|
+
while (Date.now() - startTime < timeout) {
|
|
944
|
+
try {
|
|
945
|
+
if (await this.checkAgentRegistration(sessionName, role)) {
|
|
946
|
+
this.logger.info('Fast registration confirmation', { sessionName, role });
|
|
947
|
+
return true;
|
|
948
|
+
}
|
|
949
|
+
await delay(fastCheckInterval);
|
|
950
|
+
}
|
|
951
|
+
catch (error) {
|
|
952
|
+
this.logger.debug('Error in fast registration check', {
|
|
953
|
+
sessionName,
|
|
954
|
+
role,
|
|
955
|
+
error: error instanceof Error ? error.message : String(error),
|
|
956
|
+
});
|
|
957
|
+
await delay(1000); // Shorter error delay
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
this.logger.warn('Fast registration timeout', { sessionName, role, timeout });
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Create orchestrator session - extracted from the original tmux service
|
|
965
|
+
*/
|
|
966
|
+
async createOrchestratorSession(config) {
|
|
967
|
+
this.logger.info('Creating orchestrator session', {
|
|
968
|
+
sessionName: config.sessionName,
|
|
969
|
+
projectPath: config.projectPath,
|
|
970
|
+
});
|
|
971
|
+
// Check if session already exists
|
|
972
|
+
if (await (await this.getSessionHelper()).sessionExists(config.sessionName)) {
|
|
973
|
+
this.logger.info('Orchestrator session already exists', {
|
|
974
|
+
sessionName: config.sessionName,
|
|
975
|
+
});
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
// Create new session for orchestrator
|
|
979
|
+
await (await this.getSessionHelper()).createSession(config.sessionName, config.projectPath
|
|
980
|
+
// windowName not used in PTY backend
|
|
981
|
+
);
|
|
982
|
+
this.logger.info('Orchestrator session created successfully', {
|
|
983
|
+
sessionName: config.sessionName,
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Unified session creation that handles both orchestrator and team members
|
|
988
|
+
* @param config Session configuration
|
|
989
|
+
* @returns Promise with success/error information
|
|
990
|
+
*/
|
|
991
|
+
async createAgentSession(config) {
|
|
992
|
+
const { sessionName, role, projectPath = process.cwd(), windowName, memberId } = config;
|
|
993
|
+
// Get runtime type from config or default to claude-code
|
|
994
|
+
let runtimeType = config.runtimeType || RUNTIME_TYPES.CLAUDE_CODE;
|
|
995
|
+
// Resolve runtime flags from the agent's effective skills
|
|
996
|
+
let runtimeFlags = [];
|
|
997
|
+
// For team members, try to get runtime type from storage and resolve skill flags
|
|
998
|
+
if (!config.runtimeType && role !== ORCHESTRATOR_ROLE) {
|
|
999
|
+
try {
|
|
1000
|
+
const teams = await this.storageService.getTeams();
|
|
1001
|
+
for (const team of teams) {
|
|
1002
|
+
const member = team.members?.find((m) => m.sessionName === sessionName);
|
|
1003
|
+
if (member) {
|
|
1004
|
+
if (member.runtimeType) {
|
|
1005
|
+
runtimeType = member.runtimeType;
|
|
1006
|
+
}
|
|
1007
|
+
// Resolve runtime flags from role skills + member overrides
|
|
1008
|
+
runtimeFlags = await this.resolveRuntimeFlags(role, runtimeType, member.skillOverrides, member.excludedRoleSkills);
|
|
1009
|
+
break;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
catch (error) {
|
|
1014
|
+
this.logger.warn('Failed to get runtime type or flags from storage, using defaults', {
|
|
1015
|
+
sessionName,
|
|
1016
|
+
role,
|
|
1017
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
else if (role !== ORCHESTRATOR_ROLE) {
|
|
1022
|
+
// Config-provided runtimeType, still resolve flags
|
|
1023
|
+
try {
|
|
1024
|
+
const teams = await this.storageService.getTeams();
|
|
1025
|
+
for (const team of teams) {
|
|
1026
|
+
const member = team.members?.find((m) => m.sessionName === sessionName);
|
|
1027
|
+
if (member) {
|
|
1028
|
+
runtimeFlags = await this.resolveRuntimeFlags(role, runtimeType, member.skillOverrides, member.excludedRoleSkills);
|
|
1029
|
+
break;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
catch (error) {
|
|
1034
|
+
this.logger.warn('Failed to resolve runtime flags', {
|
|
1035
|
+
sessionName, role,
|
|
1036
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
// For orchestrator, try to get runtime type from orchestrator status
|
|
1041
|
+
if (role === ORCHESTRATOR_ROLE && !config.runtimeType) {
|
|
1042
|
+
try {
|
|
1043
|
+
const orchestratorStatus = await this.storageService.getOrchestratorStatus();
|
|
1044
|
+
if (orchestratorStatus?.runtimeType) {
|
|
1045
|
+
runtimeType = orchestratorStatus.runtimeType;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
catch (error) {
|
|
1049
|
+
this.logger.warn('Failed to get orchestrator runtime type from storage, using default', {
|
|
1050
|
+
sessionName,
|
|
1051
|
+
role,
|
|
1052
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
try {
|
|
1057
|
+
this.logger.info('Creating agent session (unified approach)', {
|
|
1058
|
+
sessionName,
|
|
1059
|
+
role,
|
|
1060
|
+
runtimeType,
|
|
1061
|
+
projectPath,
|
|
1062
|
+
windowName,
|
|
1063
|
+
memberId,
|
|
1064
|
+
});
|
|
1065
|
+
// Get session helper first
|
|
1066
|
+
this.logger.debug('Getting session helper for session creation');
|
|
1067
|
+
const sessionHelper = await this.getSessionHelper();
|
|
1068
|
+
this.logger.debug('Session helper obtained', {
|
|
1069
|
+
hasHelper: !!sessionHelper,
|
|
1070
|
+
});
|
|
1071
|
+
// Check if session already exists
|
|
1072
|
+
const sessionExists = sessionHelper.sessionExists(sessionName);
|
|
1073
|
+
this.logger.debug('Checked if session exists', {
|
|
1074
|
+
sessionName,
|
|
1075
|
+
sessionExists,
|
|
1076
|
+
});
|
|
1077
|
+
if (!sessionExists) {
|
|
1078
|
+
// Session doesn't exist, go directly to creating a new session
|
|
1079
|
+
this.logger.info('Session does not exist, will create new session', { sessionName });
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
this.logger.info('Session already exists, attempting intelligent recovery instead of killing', {
|
|
1083
|
+
sessionName,
|
|
1084
|
+
});
|
|
1085
|
+
// Step 1: Try to detect if runtime is already running using slash command
|
|
1086
|
+
const runtimeService = this.createRuntimeService(runtimeType);
|
|
1087
|
+
let recoverySuccess = false;
|
|
1088
|
+
try {
|
|
1089
|
+
const runtimeRunning = await runtimeService.detectRuntimeWithCommand(sessionName);
|
|
1090
|
+
if (runtimeRunning) {
|
|
1091
|
+
this.logger.info('Runtime detected in existing session, attempting direct registration', {
|
|
1092
|
+
sessionName,
|
|
1093
|
+
});
|
|
1094
|
+
// Try to send registration prompt directly
|
|
1095
|
+
const registrationResult = await this.attemptRegistrationWithVerification(sessionName, role, 25000, // 25 second timeout
|
|
1096
|
+
memberId, 1, // single attempt for now
|
|
1097
|
+
true, // skip initial cleanup since runtime is confirmed running
|
|
1098
|
+
runtimeType);
|
|
1099
|
+
if (registrationResult) {
|
|
1100
|
+
recoverySuccess = true;
|
|
1101
|
+
this.logger.info('Successfully registered existing session with runtime', { sessionName });
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
// Step 2: If runtime not detected or registration failed, try Ctrl+C restart
|
|
1105
|
+
if (!recoverySuccess) {
|
|
1106
|
+
this.logger.info('Runtime not detected or registration failed, attempting Ctrl+C restart', {
|
|
1107
|
+
sessionName,
|
|
1108
|
+
});
|
|
1109
|
+
// Send Ctrl+C twice to try to restart Claude
|
|
1110
|
+
await (await this.getSessionHelper()).sendCtrlC(sessionName);
|
|
1111
|
+
await delay(1000);
|
|
1112
|
+
await (await this.getSessionHelper()).sendCtrlC(sessionName);
|
|
1113
|
+
await delay(2000);
|
|
1114
|
+
// Clear runtime detection cache after Ctrl+C restart
|
|
1115
|
+
runtimeService.clearDetectionCache(sessionName);
|
|
1116
|
+
// Try registration after Ctrl+C restart
|
|
1117
|
+
const postCtrlCResult = await this.attemptRegistrationWithVerification(sessionName, role, 25000, memberId, 1, // single attempt
|
|
1118
|
+
false, // don't skip cleanup after Ctrl+C
|
|
1119
|
+
runtimeType);
|
|
1120
|
+
if (postCtrlCResult) {
|
|
1121
|
+
recoverySuccess = true;
|
|
1122
|
+
this.logger.info('Successfully recovered session after Ctrl+C restart', { sessionName });
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
catch (error) {
|
|
1127
|
+
this.logger.warn('Error during intelligent session recovery', {
|
|
1128
|
+
sessionName,
|
|
1129
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
// If recovery succeeded, register for persistence and return early
|
|
1133
|
+
if (recoverySuccess) {
|
|
1134
|
+
// Register session for state persistence so it survives restarts
|
|
1135
|
+
try {
|
|
1136
|
+
const persistence = getSessionStatePersistence();
|
|
1137
|
+
persistence.registerSession(sessionName, {
|
|
1138
|
+
cwd: projectPath || process.cwd(),
|
|
1139
|
+
command: process.env.SHELL || '/bin/bash',
|
|
1140
|
+
args: [],
|
|
1141
|
+
}, runtimeType, role, config.teamId);
|
|
1142
|
+
}
|
|
1143
|
+
catch (persistError) {
|
|
1144
|
+
this.logger.warn('Failed to register recovered session for persistence (non-critical)', {
|
|
1145
|
+
sessionName,
|
|
1146
|
+
error: persistError instanceof Error ? persistError.message : String(persistError),
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
return {
|
|
1150
|
+
success: true,
|
|
1151
|
+
sessionName,
|
|
1152
|
+
message: 'Agent session recovered and registered successfully',
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
// Step 3: Last resort - fall back to killing session for clean restart
|
|
1156
|
+
this.logger.warn('All recovery attempts failed, falling back to session recreation', {
|
|
1157
|
+
sessionName,
|
|
1158
|
+
});
|
|
1159
|
+
await (await this.getSessionHelper()).killSession(sessionName);
|
|
1160
|
+
await delay(1000); // Wait for cleanup
|
|
1161
|
+
}
|
|
1162
|
+
// Create new session (same approach for both orchestrator and team members)
|
|
1163
|
+
const cwdToUse = projectPath || process.cwd();
|
|
1164
|
+
this.logger.info('Creating PTY session', {
|
|
1165
|
+
sessionName,
|
|
1166
|
+
cwd: cwdToUse,
|
|
1167
|
+
});
|
|
1168
|
+
try {
|
|
1169
|
+
const createdSession = await sessionHelper.createSession(sessionName, cwdToUse);
|
|
1170
|
+
this.logger.info('PTY session created successfully', {
|
|
1171
|
+
sessionName,
|
|
1172
|
+
pid: createdSession.pid,
|
|
1173
|
+
cwd: createdSession.cwd,
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
catch (createError) {
|
|
1177
|
+
this.logger.error('Failed to create PTY session', {
|
|
1178
|
+
sessionName,
|
|
1179
|
+
cwd: cwdToUse,
|
|
1180
|
+
error: createError instanceof Error ? createError.message : String(createError),
|
|
1181
|
+
stack: createError instanceof Error ? createError.stack : undefined,
|
|
1182
|
+
});
|
|
1183
|
+
throw createError;
|
|
1184
|
+
}
|
|
1185
|
+
// Verify session was created
|
|
1186
|
+
const sessionCreatedCheck = sessionHelper.sessionExists(sessionName);
|
|
1187
|
+
this.logger.info('Session creation verification', {
|
|
1188
|
+
sessionName,
|
|
1189
|
+
exists: sessionCreatedCheck,
|
|
1190
|
+
});
|
|
1191
|
+
if (!sessionCreatedCheck) {
|
|
1192
|
+
throw new Error(`Session '${sessionName}' was not created successfully`);
|
|
1193
|
+
}
|
|
1194
|
+
// Register session for state persistence so it survives restarts
|
|
1195
|
+
try {
|
|
1196
|
+
const persistence = getSessionStatePersistence();
|
|
1197
|
+
persistence.registerSession(sessionName, {
|
|
1198
|
+
cwd: cwdToUse,
|
|
1199
|
+
command: process.env.SHELL || '/bin/bash',
|
|
1200
|
+
args: [],
|
|
1201
|
+
}, runtimeType, role, config.teamId);
|
|
1202
|
+
}
|
|
1203
|
+
catch (persistError) {
|
|
1204
|
+
this.logger.warn('Failed to register session for persistence (non-critical)', {
|
|
1205
|
+
sessionName,
|
|
1206
|
+
error: persistError instanceof Error ? persistError.message : String(persistError),
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
// Set environment variables for MCP connection
|
|
1210
|
+
await sessionHelper.setEnvironmentVariable(sessionName, ENV_CONSTANTS.TMUX_SESSION_NAME, sessionName);
|
|
1211
|
+
await sessionHelper.setEnvironmentVariable(sessionName, ENV_CONSTANTS.CREWLY_ROLE, role);
|
|
1212
|
+
await sessionHelper.setEnvironmentVariable(sessionName, ENV_CONSTANTS.CREWLY_API_URL, `http://localhost:${WEB_CONSTANTS.PORTS.BACKEND}`);
|
|
1213
|
+
this.logger.info('Agent session created and environment variables set, initializing with registration', {
|
|
1214
|
+
sessionName,
|
|
1215
|
+
role,
|
|
1216
|
+
runtimeType,
|
|
1217
|
+
});
|
|
1218
|
+
// Use the existing unified registration system
|
|
1219
|
+
const timeout = role === ORCHESTRATOR_ROLE
|
|
1220
|
+
? AGENT_TIMEOUTS.ORCHESTRATOR_INITIALIZATION
|
|
1221
|
+
: AGENT_TIMEOUTS.REGULAR_AGENT_INITIALIZATION;
|
|
1222
|
+
const initResult = await this.initializeAgentWithRegistration(sessionName, role, projectPath, timeout, memberId, runtimeType, runtimeFlags);
|
|
1223
|
+
if (!initResult.success) {
|
|
1224
|
+
return {
|
|
1225
|
+
success: false,
|
|
1226
|
+
sessionName,
|
|
1227
|
+
error: initResult.error || 'Failed to initialize and register agent',
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
return {
|
|
1231
|
+
success: true,
|
|
1232
|
+
sessionName,
|
|
1233
|
+
message: initResult.message || 'Agent session created and registered successfully',
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
catch (error) {
|
|
1237
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1238
|
+
this.logger.error('Failed to create agent session', {
|
|
1239
|
+
sessionName,
|
|
1240
|
+
role,
|
|
1241
|
+
error: errorMessage,
|
|
1242
|
+
});
|
|
1243
|
+
return {
|
|
1244
|
+
success: false,
|
|
1245
|
+
sessionName,
|
|
1246
|
+
error: errorMessage,
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Unified session termination that handles both orchestrator and team members
|
|
1252
|
+
* @param sessionName The session to terminate
|
|
1253
|
+
* @param role The role for proper status updates
|
|
1254
|
+
* @returns Promise with success/error information
|
|
1255
|
+
*/
|
|
1256
|
+
async terminateAgentSession(sessionName, role = 'unknown') {
|
|
1257
|
+
try {
|
|
1258
|
+
this.logger.info('Terminating agent session (unified approach)', { sessionName, role });
|
|
1259
|
+
// Stop runtime exit monitoring before killing the session
|
|
1260
|
+
RuntimeExitMonitorService.getInstance().stopMonitoring(sessionName);
|
|
1261
|
+
// Get session helper once to avoid repeated async calls
|
|
1262
|
+
const sessionHelper = await this.getSessionHelper();
|
|
1263
|
+
const sessionExists = sessionHelper.sessionExists(sessionName);
|
|
1264
|
+
if (sessionExists) {
|
|
1265
|
+
// Kill the tmux session
|
|
1266
|
+
await sessionHelper.killSession(sessionName);
|
|
1267
|
+
this.logger.info('Session terminated successfully', { sessionName });
|
|
1268
|
+
}
|
|
1269
|
+
else {
|
|
1270
|
+
this.logger.info('Session already terminated or does not exist', { sessionName });
|
|
1271
|
+
}
|
|
1272
|
+
// Capture session end for memory persistence
|
|
1273
|
+
try {
|
|
1274
|
+
const sessionMemoryService = SessionMemoryService.getInstance();
|
|
1275
|
+
await sessionMemoryService.onSessionEnd(sessionName, role, process.cwd());
|
|
1276
|
+
this.logger.info('Session memory captured on termination', { sessionName, role });
|
|
1277
|
+
}
|
|
1278
|
+
catch (memoryError) {
|
|
1279
|
+
this.logger.warn('Failed to capture session memory on termination (non-critical)', {
|
|
1280
|
+
sessionName,
|
|
1281
|
+
error: memoryError instanceof Error ? memoryError.message : String(memoryError),
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
// Update agent status to inactive (works for both orchestrator and team members)
|
|
1285
|
+
await this.storageService.updateAgentStatus(sessionName, CREWLY_CONSTANTS.AGENT_STATUSES.INACTIVE);
|
|
1286
|
+
this.logger.info('Agent status updated to inactive', { sessionName, role });
|
|
1287
|
+
// Unregister from persistence so explicitly stopped agents don't reappear in resume popup
|
|
1288
|
+
try {
|
|
1289
|
+
const persistence = getSessionStatePersistence();
|
|
1290
|
+
persistence.unregisterSession(sessionName);
|
|
1291
|
+
}
|
|
1292
|
+
catch (persistError) {
|
|
1293
|
+
this.logger.warn('Failed to unregister session from persistence (non-critical)', {
|
|
1294
|
+
sessionName,
|
|
1295
|
+
error: persistError instanceof Error ? persistError.message : String(persistError),
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
return {
|
|
1299
|
+
success: true,
|
|
1300
|
+
message: sessionExists
|
|
1301
|
+
? 'Agent session terminated successfully'
|
|
1302
|
+
: 'Agent session was already terminated',
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
catch (error) {
|
|
1306
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1307
|
+
this.logger.error('Failed to terminate agent session', {
|
|
1308
|
+
sessionName,
|
|
1309
|
+
role,
|
|
1310
|
+
error: errorMessage,
|
|
1311
|
+
});
|
|
1312
|
+
return {
|
|
1313
|
+
success: false,
|
|
1314
|
+
error: errorMessage,
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Send a message to any agent session with reliable delivery.
|
|
1320
|
+
* Uses robust delivery mechanism with retry logic to ensure messages
|
|
1321
|
+
* are properly delivered to Claude Code's input.
|
|
1322
|
+
*
|
|
1323
|
+
* @param sessionName - The agent session name
|
|
1324
|
+
* @param message - The message to send
|
|
1325
|
+
* @returns Promise with success status and optional error message
|
|
1326
|
+
*
|
|
1327
|
+
* @example
|
|
1328
|
+
* ```typescript
|
|
1329
|
+
* const result = await agentRegistrationService.sendMessageToAgent(
|
|
1330
|
+
* 'crewly-orc',
|
|
1331
|
+
* '[CHAT:123] Hello, orchestrator!'
|
|
1332
|
+
* );
|
|
1333
|
+
* if (result.success) {
|
|
1334
|
+
* console.log('Message delivered');
|
|
1335
|
+
* } else {
|
|
1336
|
+
* console.error('Delivery failed:', result.error);
|
|
1337
|
+
* }
|
|
1338
|
+
* ```
|
|
1339
|
+
*/
|
|
1340
|
+
async sendMessageToAgent(sessionName, message, runtimeType = RUNTIME_TYPES.CLAUDE_CODE) {
|
|
1341
|
+
try {
|
|
1342
|
+
if (!message || typeof message !== 'string') {
|
|
1343
|
+
return {
|
|
1344
|
+
success: false,
|
|
1345
|
+
error: 'Message is required and must be a string',
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
// Get session helper once for this method
|
|
1349
|
+
const sessionHelper = await this.getSessionHelper();
|
|
1350
|
+
// Check if session exists
|
|
1351
|
+
if (!sessionHelper.sessionExists(sessionName)) {
|
|
1352
|
+
return {
|
|
1353
|
+
success: false,
|
|
1354
|
+
error: `Session '${sessionName}' does not exist`,
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
// Use robust message delivery with proper waiting mechanism
|
|
1358
|
+
const delivered = await this.sendMessageWithRetry(sessionName, message, 3, runtimeType);
|
|
1359
|
+
if (!delivered) {
|
|
1360
|
+
return {
|
|
1361
|
+
success: false,
|
|
1362
|
+
error: 'Failed to deliver message after multiple attempts',
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
this.logger.info('Message sent to agent successfully', {
|
|
1366
|
+
sessionName,
|
|
1367
|
+
messageLength: message.length,
|
|
1368
|
+
});
|
|
1369
|
+
return {
|
|
1370
|
+
success: true,
|
|
1371
|
+
message: 'Message sent to agent successfully',
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
catch (error) {
|
|
1375
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1376
|
+
this.logger.error('Failed to send message to agent', {
|
|
1377
|
+
sessionName,
|
|
1378
|
+
error: errorMessage,
|
|
1379
|
+
});
|
|
1380
|
+
return {
|
|
1381
|
+
success: false,
|
|
1382
|
+
error: errorMessage,
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Wait for an agent session to be at a ready prompt before sending messages.
|
|
1388
|
+
*
|
|
1389
|
+
* Subscribes to terminal output and polls capturePane to detect when the
|
|
1390
|
+
* agent returns to an input prompt. This is critical for sequential message
|
|
1391
|
+
* processing: after the orchestrator responds to a message it may continue
|
|
1392
|
+
* working (managing agents, running commands) before returning to prompt.
|
|
1393
|
+
*
|
|
1394
|
+
* @param sessionName - The session name to wait on
|
|
1395
|
+
* @param timeoutMs - Maximum time to wait (default 120s)
|
|
1396
|
+
* @returns true if agent is ready, false if timed out
|
|
1397
|
+
*/
|
|
1398
|
+
async waitForAgentReady(sessionName, timeoutMs = EVENT_DELIVERY_CONSTANTS.AGENT_READY_TIMEOUT, runtimeType) {
|
|
1399
|
+
const sessionHelper = await this.getSessionHelper();
|
|
1400
|
+
// Check if session exists
|
|
1401
|
+
if (!sessionHelper.sessionExists(sessionName)) {
|
|
1402
|
+
this.logger.warn('Session does not exist for waitForAgentReady', { sessionName });
|
|
1403
|
+
return false;
|
|
1404
|
+
}
|
|
1405
|
+
// Quick check - already at prompt?
|
|
1406
|
+
const currentOutput = sessionHelper.capturePane(sessionName);
|
|
1407
|
+
if (this.isClaudeAtPrompt(currentOutput, runtimeType)) {
|
|
1408
|
+
this.logger.debug('Agent already at prompt', { sessionName });
|
|
1409
|
+
return true;
|
|
1410
|
+
}
|
|
1411
|
+
this.logger.info('Waiting for agent to return to prompt', { sessionName, timeoutMs });
|
|
1412
|
+
const session = sessionHelper.getSession(sessionName);
|
|
1413
|
+
if (!session) {
|
|
1414
|
+
return false;
|
|
1415
|
+
}
|
|
1416
|
+
// Use runtime-specific pattern for stream detection to avoid false positives
|
|
1417
|
+
// (e.g. Gemini's `> ` pattern matching markdown blockquotes in Claude Code output)
|
|
1418
|
+
const isClaudeCode = runtimeType === RUNTIME_TYPES.CLAUDE_CODE;
|
|
1419
|
+
const isGemini = runtimeType === RUNTIME_TYPES.GEMINI_CLI;
|
|
1420
|
+
const streamPattern = isClaudeCode
|
|
1421
|
+
? TERMINAL_PATTERNS.CLAUDE_CODE_PROMPT
|
|
1422
|
+
: isGemini
|
|
1423
|
+
? TERMINAL_PATTERNS.GEMINI_CLI_PROMPT
|
|
1424
|
+
: TERMINAL_PATTERNS.PROMPT_STREAM;
|
|
1425
|
+
return new Promise((resolve) => {
|
|
1426
|
+
let resolved = false;
|
|
1427
|
+
const pollInterval = EVENT_DELIVERY_CONSTANTS.AGENT_READY_POLL_INTERVAL;
|
|
1428
|
+
const cleanup = () => {
|
|
1429
|
+
if (resolved)
|
|
1430
|
+
return;
|
|
1431
|
+
resolved = true;
|
|
1432
|
+
clearTimeout(timeoutId);
|
|
1433
|
+
clearInterval(pollId);
|
|
1434
|
+
unsubscribe();
|
|
1435
|
+
};
|
|
1436
|
+
// Timeout - give up waiting
|
|
1437
|
+
const timeoutId = setTimeout(() => {
|
|
1438
|
+
this.logger.warn('Timed out waiting for agent to be ready', { sessionName, timeoutMs });
|
|
1439
|
+
cleanup();
|
|
1440
|
+
resolve(false);
|
|
1441
|
+
}, timeoutMs);
|
|
1442
|
+
// Poll capturePane periodically as a fallback
|
|
1443
|
+
const pollId = setInterval(() => {
|
|
1444
|
+
if (resolved)
|
|
1445
|
+
return;
|
|
1446
|
+
const output = sessionHelper.capturePane(sessionName);
|
|
1447
|
+
if (this.isClaudeAtPrompt(output, runtimeType)) {
|
|
1448
|
+
this.logger.debug('Agent at prompt (detected via polling)', { sessionName });
|
|
1449
|
+
cleanup();
|
|
1450
|
+
resolve(true);
|
|
1451
|
+
}
|
|
1452
|
+
}, pollInterval);
|
|
1453
|
+
// Also subscribe to terminal data for faster detection
|
|
1454
|
+
const unsubscribe = session.onData((data) => {
|
|
1455
|
+
if (resolved)
|
|
1456
|
+
return;
|
|
1457
|
+
if (streamPattern.test(data)) {
|
|
1458
|
+
// Double-check with capturePane to avoid false positives from partial data
|
|
1459
|
+
const output = sessionHelper.capturePane(sessionName);
|
|
1460
|
+
if (this.isClaudeAtPrompt(output, runtimeType)) {
|
|
1461
|
+
this.logger.debug('Agent at prompt (detected via stream)', { sessionName });
|
|
1462
|
+
cleanup();
|
|
1463
|
+
resolve(true);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
});
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* @deprecated Replaced by SessionCommandHelper.sendMessage() in sendMessageWithRetry.
|
|
1471
|
+
* Complex event-driven state machine was fragile — Enter key often got lost.
|
|
1472
|
+
* Kept as dead code for reference during transition.
|
|
1473
|
+
*/
|
|
1474
|
+
async _deprecated_sendMessageEventDriven(sessionName, message, timeoutMs = EVENT_DELIVERY_CONSTANTS.TOTAL_DELIVERY_TIMEOUT) {
|
|
1475
|
+
const sessionHelper = await this.getSessionHelper();
|
|
1476
|
+
const session = sessionHelper.getSession(sessionName);
|
|
1477
|
+
if (!session) {
|
|
1478
|
+
this.logger.error('Session not found for event-driven delivery', { sessionName });
|
|
1479
|
+
return false;
|
|
1480
|
+
}
|
|
1481
|
+
return new Promise((resolve) => {
|
|
1482
|
+
let buffer = '';
|
|
1483
|
+
let messageSent = false;
|
|
1484
|
+
let enterSent = false;
|
|
1485
|
+
let enterAccepted = false;
|
|
1486
|
+
let deliveryConfirmed = false;
|
|
1487
|
+
let resolved = false;
|
|
1488
|
+
// Track all timeouts to prevent memory leaks (P1.1 fix)
|
|
1489
|
+
const pendingTimeouts = [];
|
|
1490
|
+
const scheduleTimeout = (fn, delayMs) => {
|
|
1491
|
+
const id = setTimeout(fn, delayMs);
|
|
1492
|
+
pendingTimeouts.push(id);
|
|
1493
|
+
return id;
|
|
1494
|
+
};
|
|
1495
|
+
const cleanup = () => {
|
|
1496
|
+
// Immediately mark as resolved to prevent race conditions (P1.2 fix)
|
|
1497
|
+
const wasResolved = resolved;
|
|
1498
|
+
resolved = true;
|
|
1499
|
+
if (!wasResolved) {
|
|
1500
|
+
// Clear all pending timeouts to prevent memory leaks
|
|
1501
|
+
pendingTimeouts.forEach((id) => clearTimeout(id));
|
|
1502
|
+
clearTimeout(timeoutId);
|
|
1503
|
+
unsubscribe();
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
// Use centralized patterns from TERMINAL_PATTERNS
|
|
1507
|
+
const PASTE_PATTERN = TERMINAL_PATTERNS.PASTE_INDICATOR;
|
|
1508
|
+
const PROCESSING_PATTERN = TERMINAL_PATTERNS.PROCESSING;
|
|
1509
|
+
// Use centralized timing from EVENT_DELIVERY_CONSTANTS
|
|
1510
|
+
const INITIAL_DELAY = EVENT_DELIVERY_CONSTANTS.INITIAL_MESSAGE_DELAY;
|
|
1511
|
+
const PASTE_CHECK_DELAY = EVENT_DELIVERY_CONSTANTS.PASTE_CHECK_DELAY;
|
|
1512
|
+
const ENTER_RETRY_DELAY = EVENT_DELIVERY_CONSTANTS.ENTER_RETRY_DELAY;
|
|
1513
|
+
const MAX_ENTER_RETRIES = EVENT_DELIVERY_CONSTANTS.MAX_ENTER_RETRIES;
|
|
1514
|
+
const MAX_BUFFER_SIZE = EVENT_DELIVERY_CONSTANTS.MAX_BUFFER_SIZE;
|
|
1515
|
+
// Helper to send the message when prompt is detected
|
|
1516
|
+
const sendMessageNow = () => {
|
|
1517
|
+
if (messageSent || resolved)
|
|
1518
|
+
return;
|
|
1519
|
+
this.logger.debug('Claude at prompt, sending message', {
|
|
1520
|
+
sessionName,
|
|
1521
|
+
messageLength: message.length,
|
|
1522
|
+
isMultiLine: message.includes('\n'),
|
|
1523
|
+
});
|
|
1524
|
+
// Send the message text
|
|
1525
|
+
session.write(message);
|
|
1526
|
+
messageSent = true;
|
|
1527
|
+
const isMultiLine = message.includes('\n');
|
|
1528
|
+
// Track Enter key state
|
|
1529
|
+
let enterAttempts = 0;
|
|
1530
|
+
let processingDetected = false;
|
|
1531
|
+
const bufferAtSend = buffer;
|
|
1532
|
+
// Function to send Enter and track attempts
|
|
1533
|
+
const sendEnterKey = (reason) => {
|
|
1534
|
+
if (resolved || processingDetected)
|
|
1535
|
+
return;
|
|
1536
|
+
enterAttempts++;
|
|
1537
|
+
session.write('\r');
|
|
1538
|
+
enterSent = true;
|
|
1539
|
+
this.logger.debug('Enter key sent', {
|
|
1540
|
+
sessionName,
|
|
1541
|
+
attempt: enterAttempts,
|
|
1542
|
+
reason,
|
|
1543
|
+
});
|
|
1544
|
+
};
|
|
1545
|
+
// Function to check if Enter was accepted (processing started)
|
|
1546
|
+
const checkProcessingStarted = () => {
|
|
1547
|
+
const newData = buffer.slice(bufferAtSend.length);
|
|
1548
|
+
return PROCESSING_PATTERN.test(newData);
|
|
1549
|
+
};
|
|
1550
|
+
// Function to check for paste indicator
|
|
1551
|
+
const checkPasteIndicator = () => {
|
|
1552
|
+
const newData = buffer.slice(bufferAtSend.length);
|
|
1553
|
+
return PASTE_PATTERN.test(newData);
|
|
1554
|
+
};
|
|
1555
|
+
// Strategy: Send Enter with progressive timing, retry if not accepted
|
|
1556
|
+
const attemptEnter = (attemptNum) => {
|
|
1557
|
+
if (resolved || processingDetected)
|
|
1558
|
+
return;
|
|
1559
|
+
// Check if processing already started
|
|
1560
|
+
if (checkProcessingStarted()) {
|
|
1561
|
+
processingDetected = true;
|
|
1562
|
+
enterAccepted = true;
|
|
1563
|
+
this.logger.debug('Processing detected, message accepted', { sessionName, attemptNum });
|
|
1564
|
+
buffer = ''; // Reset for processing indicator detection
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
if (attemptNum > MAX_ENTER_RETRIES) {
|
|
1568
|
+
this.logger.warn('Max Enter retries reached, verifying message acceptance', { sessionName });
|
|
1569
|
+
scheduleTimeout(async () => {
|
|
1570
|
+
if (resolved)
|
|
1571
|
+
return;
|
|
1572
|
+
const stuck = await this.isMessageStuckAtPrompt(sessionName, message);
|
|
1573
|
+
if (stuck) {
|
|
1574
|
+
this.logger.warn('Message stuck at prompt after all Enter retries', { sessionName });
|
|
1575
|
+
const stuckHelper = await this.getSessionHelper();
|
|
1576
|
+
await stuckHelper.clearCurrentCommandLine(sessionName);
|
|
1577
|
+
enterAccepted = false;
|
|
1578
|
+
}
|
|
1579
|
+
else {
|
|
1580
|
+
this.logger.debug('Message appears accepted (no longer at prompt)', { sessionName });
|
|
1581
|
+
enterAccepted = true;
|
|
1582
|
+
buffer = '';
|
|
1583
|
+
}
|
|
1584
|
+
}, EVENT_DELIVERY_CONSTANTS.POST_ENTER_VERIFICATION_DELAY);
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
sendEnterKey(attemptNum === 1 ? 'initial' : `retry-${attemptNum}`);
|
|
1588
|
+
// Schedule check and possible retry (using tracked timeout to prevent leaks)
|
|
1589
|
+
scheduleTimeout(() => {
|
|
1590
|
+
if (resolved)
|
|
1591
|
+
return;
|
|
1592
|
+
if (checkProcessingStarted()) {
|
|
1593
|
+
processingDetected = true;
|
|
1594
|
+
enterAccepted = true;
|
|
1595
|
+
this.logger.debug('Processing detected after Enter', { sessionName, attemptNum });
|
|
1596
|
+
buffer = '';
|
|
1597
|
+
}
|
|
1598
|
+
else {
|
|
1599
|
+
// Not accepted yet, retry
|
|
1600
|
+
this.logger.debug('Enter may not have been accepted, retrying', {
|
|
1601
|
+
sessionName,
|
|
1602
|
+
attemptNum,
|
|
1603
|
+
bufferLength: buffer.length,
|
|
1604
|
+
});
|
|
1605
|
+
attemptEnter(attemptNum + 1);
|
|
1606
|
+
}
|
|
1607
|
+
}, ENTER_RETRY_DELAY);
|
|
1608
|
+
};
|
|
1609
|
+
// For multi-line messages, wait longer for paste indicator
|
|
1610
|
+
// For single-line messages, send Enter sooner
|
|
1611
|
+
const initialWait = isMultiLine ? PASTE_CHECK_DELAY : INITIAL_DELAY;
|
|
1612
|
+
scheduleTimeout(() => {
|
|
1613
|
+
if (resolved)
|
|
1614
|
+
return;
|
|
1615
|
+
// For multi-line: check if paste indicator appeared
|
|
1616
|
+
if (isMultiLine && checkPasteIndicator()) {
|
|
1617
|
+
this.logger.debug('Paste indicator detected', { sessionName });
|
|
1618
|
+
}
|
|
1619
|
+
// Start Enter key attempts
|
|
1620
|
+
attemptEnter(1);
|
|
1621
|
+
}, initialWait);
|
|
1622
|
+
};
|
|
1623
|
+
const timeoutId = setTimeout(async () => {
|
|
1624
|
+
this.logger.debug('Event-driven delivery timed out', {
|
|
1625
|
+
sessionName,
|
|
1626
|
+
messageSent,
|
|
1627
|
+
enterSent,
|
|
1628
|
+
enterAccepted,
|
|
1629
|
+
deliveryConfirmed,
|
|
1630
|
+
bufferLength: buffer.length,
|
|
1631
|
+
});
|
|
1632
|
+
// If Enter was sent but not confirmed accepted, verify via terminal capture
|
|
1633
|
+
if (enterSent && !enterAccepted && !deliveryConfirmed) {
|
|
1634
|
+
const timeoutHelper = await this.getSessionHelper();
|
|
1635
|
+
const stuck = await this.isMessageStuckAtPrompt(sessionName, message);
|
|
1636
|
+
if (stuck) {
|
|
1637
|
+
this.logger.warn('Timeout: message stuck at prompt, clearing and failing', { sessionName });
|
|
1638
|
+
await timeoutHelper.clearCurrentCommandLine(sessionName);
|
|
1639
|
+
cleanup();
|
|
1640
|
+
resolve(false);
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
this.logger.debug('Timeout: message not at prompt, treating as accepted', { sessionName });
|
|
1644
|
+
}
|
|
1645
|
+
cleanup();
|
|
1646
|
+
resolve(enterAccepted || deliveryConfirmed);
|
|
1647
|
+
}, timeoutMs);
|
|
1648
|
+
// IMPORTANT: Check current terminal state, but wait for output to settle first.
|
|
1649
|
+
// If the orchestrator just finished outputting (greeting, notification, status bar),
|
|
1650
|
+
// the prompt may not be cleanly detectable. We capture the pane, wait briefly,
|
|
1651
|
+
// and re-capture. If output is still changing, wait again before checking prompt.
|
|
1652
|
+
// Use 50 lines to account for status bars and notifications that can
|
|
1653
|
+
// wrap across many lines and push the prompt out of a smaller window.
|
|
1654
|
+
const waitForSettled = async () => {
|
|
1655
|
+
let prevOutput = sessionHelper.capturePane(sessionName);
|
|
1656
|
+
for (let i = 0; i < 5; i++) { // Max 5 checks, 500ms apart = 2.5s max
|
|
1657
|
+
if (resolved)
|
|
1658
|
+
return;
|
|
1659
|
+
await delay(500);
|
|
1660
|
+
if (resolved)
|
|
1661
|
+
return;
|
|
1662
|
+
const currentOutput = sessionHelper.capturePane(sessionName);
|
|
1663
|
+
if (currentOutput === prevOutput) {
|
|
1664
|
+
// Output settled
|
|
1665
|
+
if (this.isClaudeAtPrompt(currentOutput)) {
|
|
1666
|
+
this.logger.debug('Claude at prompt after output settled', { sessionName, settleChecks: i + 1 });
|
|
1667
|
+
sendMessageNow();
|
|
1668
|
+
}
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
prevOutput = currentOutput;
|
|
1672
|
+
}
|
|
1673
|
+
// Output still changing after 2.5s - check anyway
|
|
1674
|
+
if (!resolved && this.isClaudeAtPrompt(prevOutput)) {
|
|
1675
|
+
this.logger.debug('Claude at prompt (output still changing, checking anyway)', { sessionName });
|
|
1676
|
+
sendMessageNow();
|
|
1677
|
+
}
|
|
1678
|
+
};
|
|
1679
|
+
waitForSettled();
|
|
1680
|
+
const unsubscribe = session.onData((data) => {
|
|
1681
|
+
if (resolved)
|
|
1682
|
+
return;
|
|
1683
|
+
// Accumulate data with size limit to prevent memory exhaustion (P2.3 fix)
|
|
1684
|
+
buffer += data;
|
|
1685
|
+
if (buffer.length > MAX_BUFFER_SIZE) {
|
|
1686
|
+
buffer = buffer.slice(-MAX_BUFFER_SIZE);
|
|
1687
|
+
}
|
|
1688
|
+
// Phase 1: Wait for Claude to be at prompt before sending
|
|
1689
|
+
if (!messageSent) {
|
|
1690
|
+
const isAtPrompt = AgentRegistrationService.CLAUDE_PROMPT_STREAM_PATTERN.test(buffer);
|
|
1691
|
+
if (isAtPrompt) {
|
|
1692
|
+
sendMessageNow();
|
|
1693
|
+
}
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
// Phase 2: Only check for processing indicators AFTER Enter has been sent
|
|
1697
|
+
if (!enterSent) {
|
|
1698
|
+
return; // Wait for Enter to be sent
|
|
1699
|
+
}
|
|
1700
|
+
// Look for processing indicators confirming delivery
|
|
1701
|
+
const hasProcessingIndicator = AgentRegistrationService.CLAUDE_PROCESSING_INDICATORS.some((pattern) => pattern.test(buffer));
|
|
1702
|
+
// Also check if prompt disappeared (Claude is working)
|
|
1703
|
+
const promptStillVisible = AgentRegistrationService.CLAUDE_PROMPT_STREAM_PATTERN.test(buffer);
|
|
1704
|
+
// Use constant for minimum buffer check (P3.2 fix)
|
|
1705
|
+
if (hasProcessingIndicator || (!promptStillVisible && buffer.length > EVENT_DELIVERY_CONSTANTS.MIN_BUFFER_FOR_PROCESSING_DETECTION)) {
|
|
1706
|
+
this.logger.debug('Message delivery confirmed (event-driven)', {
|
|
1707
|
+
sessionName,
|
|
1708
|
+
hasProcessingIndicator,
|
|
1709
|
+
promptStillVisible,
|
|
1710
|
+
bufferLength: buffer.length,
|
|
1711
|
+
});
|
|
1712
|
+
deliveryConfirmed = true;
|
|
1713
|
+
cleanup();
|
|
1714
|
+
resolve(true);
|
|
1715
|
+
}
|
|
1716
|
+
});
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* Send message with retry logic for reliable delivery to Claude Code.
|
|
1721
|
+
* Uses SessionCommandHelper.sendMessage() (proven two-step write pattern)
|
|
1722
|
+
* with stuck-message detection and retry on failure.
|
|
1723
|
+
*
|
|
1724
|
+
* @param sessionName - The session name
|
|
1725
|
+
* @param message - The message to send
|
|
1726
|
+
* @param maxAttempts - Maximum number of delivery attempts
|
|
1727
|
+
* @returns true if message was delivered successfully
|
|
1728
|
+
*/
|
|
1729
|
+
async sendMessageWithRetry(sessionName, message, maxAttempts = 3, runtimeType = RUNTIME_TYPES.CLAUDE_CODE) {
|
|
1730
|
+
const sessionHelper = await this.getSessionHelper();
|
|
1731
|
+
const isClaudeCode = runtimeType === RUNTIME_TYPES.CLAUDE_CODE;
|
|
1732
|
+
// Register TUI sessions for TUI prompt-line scanning (Part 1 of scanner).
|
|
1733
|
+
// The scanner itself is started lazily by trackSentMessage() which
|
|
1734
|
+
// runs after every message send, covering all runtimes.
|
|
1735
|
+
if (!isClaudeCode) {
|
|
1736
|
+
this.tuiSessionRegistry.set(sessionName, runtimeType);
|
|
1737
|
+
}
|
|
1738
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1739
|
+
try {
|
|
1740
|
+
this.logger.debug('Attempting message delivery', {
|
|
1741
|
+
sessionName,
|
|
1742
|
+
attempt,
|
|
1743
|
+
maxAttempts,
|
|
1744
|
+
messageLength: message.length,
|
|
1745
|
+
runtimeType,
|
|
1746
|
+
});
|
|
1747
|
+
// Verify agent is at prompt before sending
|
|
1748
|
+
const output = sessionHelper.capturePane(sessionName);
|
|
1749
|
+
if (!this.isClaudeAtPrompt(output, runtimeType)) {
|
|
1750
|
+
this.logger.debug('Not at prompt, waiting before retry', { sessionName, attempt });
|
|
1751
|
+
await delay(SESSION_COMMAND_DELAYS.CLAUDE_RECOVERY_DELAY);
|
|
1752
|
+
continue;
|
|
1753
|
+
}
|
|
1754
|
+
// Gemini CLI shell mode guard: if the prompt shows `!` instead of `>`,
|
|
1755
|
+
// input will be executed as a shell command. Send Escape to exit first.
|
|
1756
|
+
if (runtimeType === RUNTIME_TYPES.GEMINI_CLI && this.isGeminiInShellMode(output)) {
|
|
1757
|
+
const escaped = await this.escapeGeminiShellMode(sessionName, sessionHelper);
|
|
1758
|
+
if (!escaped) {
|
|
1759
|
+
this.logger.warn('Could not exit Gemini shell mode, skipping attempt', {
|
|
1760
|
+
sessionName,
|
|
1761
|
+
attempt,
|
|
1762
|
+
});
|
|
1763
|
+
await delay(SESSION_COMMAND_DELAYS.MESSAGE_RETRY_DELAY);
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
// Clear any stale text before sending.
|
|
1768
|
+
// Claude Code: Ctrl+C to cancel any pending input — but ONLY on
|
|
1769
|
+
// retry attempts. On the first attempt, waitForAgentReady already
|
|
1770
|
+
// confirmed the agent is idle at the prompt, so Ctrl+C is
|
|
1771
|
+
// unnecessary and can disrupt Claude Code's input handler if the
|
|
1772
|
+
// 300ms settling delay isn't enough.
|
|
1773
|
+
// Gemini CLI (Ink TUI): escalating recovery to refocus input box.
|
|
1774
|
+
// The TUI input can lose focus after idle periods, auto-update
|
|
1775
|
+
// notifications, or dialog overlays. When defocused, writes are
|
|
1776
|
+
// silently consumed by the Ink framework but NOT routed to the
|
|
1777
|
+
// InputPrompt.
|
|
1778
|
+
// Do NOT send Escape (defocuses permanently), Ctrl+C (triggers
|
|
1779
|
+
// /quit on empty prompt), or Ctrl+U (ignored by TUI).
|
|
1780
|
+
//
|
|
1781
|
+
// Recovery strategies (escalating per attempt):
|
|
1782
|
+
// 1. Tab + Enter: Tab triggers Ink's focusNext() which cycles
|
|
1783
|
+
// focus through focusable components. If InputPrompt is the
|
|
1784
|
+
// only/next focusable element, it gets refocused. Enter then
|
|
1785
|
+
// dismisses any overlay or acts as safe no-op on empty prompt.
|
|
1786
|
+
// 2-3. PTY resize + Tab + Enter: Resize sends SIGWINCH to the
|
|
1787
|
+
// child process, forcing Ink to re-render the entire TUI.
|
|
1788
|
+
// This may restore the internal focus state. Then Tab + Enter
|
|
1789
|
+
// for focus cycling and overlay dismissal.
|
|
1790
|
+
if (isClaudeCode) {
|
|
1791
|
+
if (attempt > 1) {
|
|
1792
|
+
await sessionHelper.sendCtrlC(sessionName);
|
|
1793
|
+
await delay(300);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
else {
|
|
1797
|
+
// On retry attempts (2+), force a PTY resize to trigger SIGWINCH.
|
|
1798
|
+
// This makes Ink re-render the TUI, potentially restoring focus state.
|
|
1799
|
+
if (attempt > 1) {
|
|
1800
|
+
try {
|
|
1801
|
+
const session = sessionHelper.getSession(sessionName);
|
|
1802
|
+
if (session) {
|
|
1803
|
+
// Resize slightly then back to trigger SIGWINCH
|
|
1804
|
+
session.resize(81, 25);
|
|
1805
|
+
await delay(200);
|
|
1806
|
+
session.resize(80, 24);
|
|
1807
|
+
await delay(500);
|
|
1808
|
+
this.logger.debug('PTY resize sent to trigger TUI re-render', {
|
|
1809
|
+
sessionName,
|
|
1810
|
+
attempt,
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
catch (resizeErr) {
|
|
1815
|
+
this.logger.debug('PTY resize failed (non-fatal)', {
|
|
1816
|
+
sessionName,
|
|
1817
|
+
error: resizeErr instanceof Error ? resizeErr.message : String(resizeErr),
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
// Send Tab to cycle Ink focus. In Ink v6, Tab triggers
|
|
1822
|
+
// focusNext() in FocusContext, which moves focus to the next
|
|
1823
|
+
// focusable component (InputPrompt). This works even when the
|
|
1824
|
+
// input is defocused because the Tab handler runs at the Ink
|
|
1825
|
+
// framework level, not the component level.
|
|
1826
|
+
await sessionHelper.sendKey(sessionName, 'Tab');
|
|
1827
|
+
await delay(300);
|
|
1828
|
+
// Then Enter to dismiss any notification overlay and ensure
|
|
1829
|
+
// the input is engaged. Enter on an empty `> ` prompt is a
|
|
1830
|
+
// safe no-op (just shows a new blank prompt line).
|
|
1831
|
+
await sessionHelper.sendEnter(sessionName);
|
|
1832
|
+
await delay(500);
|
|
1833
|
+
}
|
|
1834
|
+
// Capture output BEFORE sending to detect changes for ALL runtimes.
|
|
1835
|
+
// Claude Code: progressive output-change detection prevents false-positive
|
|
1836
|
+
// Ctrl+C by giving Claude up to 6.5s to start rendering output.
|
|
1837
|
+
// TUI runtimes: same output-change detection as before.
|
|
1838
|
+
// Use a small pane capture (20 lines) to reduce noise from TUI
|
|
1839
|
+
// border redraws that can cause length changes unrelated to delivery.
|
|
1840
|
+
const beforeOutput = sessionHelper.capturePane(sessionName, 20);
|
|
1841
|
+
const beforeLength = beforeOutput.length;
|
|
1842
|
+
// Use SessionCommandHelper.sendMessage() — proven two-step write:
|
|
1843
|
+
// 1. session.write(message) — triggers bracketed paste
|
|
1844
|
+
// 2. await delay(scaled) — waits for paste processing
|
|
1845
|
+
// 3. session.write('\r') — sends Enter separately
|
|
1846
|
+
// 4. await delay(KEY_DELAY) — waits for key processing
|
|
1847
|
+
await sessionHelper.sendMessage(sessionName, message);
|
|
1848
|
+
// Register for background stuck-detection safety net (all runtimes).
|
|
1849
|
+
// If progressive verification below misses an Enter drop, the
|
|
1850
|
+
// background scanner will catch it within 30s.
|
|
1851
|
+
this.trackSentMessage(sessionName, message);
|
|
1852
|
+
// Wait for agent to start processing, then verify delivery.
|
|
1853
|
+
// TUI runtimes need a longer delay (3s) for the TUI to redraw
|
|
1854
|
+
// and show processing indicators after accepting input.
|
|
1855
|
+
const processingDelay = isClaudeCode
|
|
1856
|
+
? SESSION_COMMAND_DELAYS.MESSAGE_PROCESSING_DELAY
|
|
1857
|
+
: 3000;
|
|
1858
|
+
await delay(processingDelay);
|
|
1859
|
+
// === Delivery verification ===
|
|
1860
|
+
// Claude Code: prompt disappears during processing, so check
|
|
1861
|
+
// isMessageStuckAtPrompt (text still at prompt = stuck).
|
|
1862
|
+
// TUI runtimes (Gemini CLI): use TWO-PHASE verification:
|
|
1863
|
+
// Phase 1: Direct check — is the message text sitting ON the
|
|
1864
|
+
// `> ` prompt line? If yes, Enter was dropped. Try
|
|
1865
|
+
// pressing Enter to recover before falling through to retry.
|
|
1866
|
+
// Phase 2: Output-change detection — compare before/after captures
|
|
1867
|
+
// to see if the agent started processing.
|
|
1868
|
+
if (isClaudeCode) {
|
|
1869
|
+
// Progressive verification for Claude Code.
|
|
1870
|
+
// Claude Code routinely takes 3-8s to start rendering with large
|
|
1871
|
+
// context. The old 2.5s window caused false-positive Ctrl+C that
|
|
1872
|
+
// cancelled valid in-progress prompts. Now we progressively check
|
|
1873
|
+
// at [1s, 2s, 3s] intervals (total ~6.5s including processing delay).
|
|
1874
|
+
//
|
|
1875
|
+
// Progressive verification: check at [1s, 2s, 3s] intervals whether
|
|
1876
|
+
// the agent accepted the message and started processing it.
|
|
1877
|
+
//
|
|
1878
|
+
// Uses runtime-agnostic detection — no hardcoded prompt characters.
|
|
1879
|
+
// This stays correct when the agent runtime upgrades its prompt.
|
|
1880
|
+
//
|
|
1881
|
+
// Three signals checked per interval:
|
|
1882
|
+
// 1. Processing indicators (⠋ ⏺ spinners) — definitive success
|
|
1883
|
+
// 2. isClaudeAtPrompt() — if still true, agent is idle, wait more
|
|
1884
|
+
// 3. Message text in bottom lines — if found, Enter was dropped
|
|
1885
|
+
// (message pasted at prompt but not submitted)
|
|
1886
|
+
// If prompt gone AND message text NOT at bottom → success
|
|
1887
|
+
let claudeDelivered = false;
|
|
1888
|
+
for (const intervalMs of SESSION_COMMAND_DELAYS.CLAUDE_VERIFICATION_INTERVALS) {
|
|
1889
|
+
const currentOutput = sessionHelper.capturePane(sessionName);
|
|
1890
|
+
// 1. Processing indicators (spinners, working marker) are
|
|
1891
|
+
// definitive proof the agent accepted and is working.
|
|
1892
|
+
// Only check spinner/⏺ chars — NOT text words like
|
|
1893
|
+
// "thinking" which appear in historical response text.
|
|
1894
|
+
if (TERMINAL_PATTERNS.PROCESSING.test(currentOutput)) {
|
|
1895
|
+
this.logger.debug('Processing indicators detected — message accepted', {
|
|
1896
|
+
sessionName,
|
|
1897
|
+
attempt,
|
|
1898
|
+
});
|
|
1899
|
+
claudeDelivered = true;
|
|
1900
|
+
break;
|
|
1901
|
+
}
|
|
1902
|
+
// 2. Is the agent still at its standard idle prompt?
|
|
1903
|
+
// Reuse isClaudeAtPrompt() — the single source of truth
|
|
1904
|
+
// for prompt detection. This avoids hardcoding prompt
|
|
1905
|
+
// characters and stays correct when the runtime upgrades.
|
|
1906
|
+
if (this.isClaudeAtPrompt(currentOutput, runtimeType)) {
|
|
1907
|
+
// Prompt is visible. Before just waiting, check if our
|
|
1908
|
+
// message text is also at the bottom — this means the
|
|
1909
|
+
// text was pasted but Enter was dropped. Press Enter.
|
|
1910
|
+
// Normalize whitespace: enhanced messages contain literal \n from
|
|
1911
|
+
// addContinuationInstructions(). join(' ') replaces line breaks with
|
|
1912
|
+
// spaces, but the snippet still has \n chars, so includes() fails.
|
|
1913
|
+
const promptMsgSnippet = (message.length > 20
|
|
1914
|
+
? message.substring(0, 80)
|
|
1915
|
+
: message).replace(/\s+/g, ' ').trim();
|
|
1916
|
+
const promptBottomLines = currentOutput.split('\n').slice(-10).join(' ').replace(/\s+/g, ' ');
|
|
1917
|
+
if (promptBottomLines.includes(promptMsgSnippet)) {
|
|
1918
|
+
this.logger.warn('At prompt with message text at bottom — pressing Enter', {
|
|
1919
|
+
sessionName,
|
|
1920
|
+
attempt,
|
|
1921
|
+
intervalMs,
|
|
1922
|
+
});
|
|
1923
|
+
await sessionHelper.sendEnter(sessionName);
|
|
1924
|
+
await delay(500);
|
|
1925
|
+
await sessionHelper.sendEnter(sessionName); // backup
|
|
1926
|
+
await delay(SESSION_COMMAND_DELAYS.MESSAGE_PROCESSING_DELAY);
|
|
1927
|
+
// Verify recovery
|
|
1928
|
+
const postEnterOutput = sessionHelper.capturePane(sessionName);
|
|
1929
|
+
if (TERMINAL_PATTERNS.PROCESSING.test(postEnterOutput) ||
|
|
1930
|
+
!this.isClaudeAtPrompt(postEnterOutput, runtimeType)) {
|
|
1931
|
+
this.logger.info('Enter recovery from prompt successful', {
|
|
1932
|
+
sessionName,
|
|
1933
|
+
attempt,
|
|
1934
|
+
});
|
|
1935
|
+
claudeDelivered = true;
|
|
1936
|
+
break;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
this.logger.debug('Agent still at prompt, waiting before re-check', {
|
|
1940
|
+
sessionName,
|
|
1941
|
+
attempt,
|
|
1942
|
+
intervalMs,
|
|
1943
|
+
});
|
|
1944
|
+
await delay(intervalMs);
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
// 3. Prompt not in its standard idle form. Before declaring
|
|
1948
|
+
// success, check if our message text is stuck at the
|
|
1949
|
+
// bottom of the terminal. When Enter is dropped, the
|
|
1950
|
+
// pasted text appears on the prompt line — the terminal
|
|
1951
|
+
// shows the prompt char followed by our message text.
|
|
1952
|
+
// This check is runtime-agnostic: it works regardless
|
|
1953
|
+
// of what prompt character the runtime uses.
|
|
1954
|
+
// Normalize whitespace in snippet (see Step 2 comment above).
|
|
1955
|
+
const msgSnippet = (message.length > 20
|
|
1956
|
+
? message.substring(0, 80)
|
|
1957
|
+
: message).replace(/\s+/g, ' ').trim();
|
|
1958
|
+
const bottomLines = currentOutput.split('\n').slice(-10).join(' ').replace(/\s+/g, ' ');
|
|
1959
|
+
if (bottomLines.includes(msgSnippet)) {
|
|
1960
|
+
// Message text is at the bottom of the terminal but the
|
|
1961
|
+
// prompt is no longer in its idle form — Enter was dropped.
|
|
1962
|
+
// Instead of waiting and doing a full Ctrl+C + resend retry,
|
|
1963
|
+
// press Enter immediately to submit the already-pasted text.
|
|
1964
|
+
this.logger.warn('Message text stuck at bottom — pressing Enter to recover', {
|
|
1965
|
+
sessionName,
|
|
1966
|
+
attempt,
|
|
1967
|
+
intervalMs,
|
|
1968
|
+
});
|
|
1969
|
+
await sessionHelper.sendEnter(sessionName);
|
|
1970
|
+
await delay(500);
|
|
1971
|
+
await sessionHelper.sendEnter(sessionName); // backup Enter
|
|
1972
|
+
// Give the agent a moment to start processing after Enter
|
|
1973
|
+
await delay(SESSION_COMMAND_DELAYS.MESSAGE_PROCESSING_DELAY);
|
|
1974
|
+
// Verify recovery: check if processing started
|
|
1975
|
+
const recoveryOutput = sessionHelper.capturePane(sessionName);
|
|
1976
|
+
if (TERMINAL_PATTERNS.PROCESSING.test(recoveryOutput)) {
|
|
1977
|
+
this.logger.info('Enter recovery successful — processing started', {
|
|
1978
|
+
sessionName,
|
|
1979
|
+
attempt,
|
|
1980
|
+
});
|
|
1981
|
+
claudeDelivered = true;
|
|
1982
|
+
break;
|
|
1983
|
+
}
|
|
1984
|
+
// Check if prompt reappeared (Enter submitted but processed instantly)
|
|
1985
|
+
// or if output changed from the stuck state
|
|
1986
|
+
const recoveryBottom = recoveryOutput.split('\n').slice(-10).join(' ').replace(/\s+/g, ' ');
|
|
1987
|
+
if (!recoveryBottom.includes(msgSnippet)) {
|
|
1988
|
+
this.logger.info('Enter recovery successful — stuck text cleared', {
|
|
1989
|
+
sessionName,
|
|
1990
|
+
attempt,
|
|
1991
|
+
});
|
|
1992
|
+
claudeDelivered = true;
|
|
1993
|
+
break;
|
|
1994
|
+
}
|
|
1995
|
+
// Still stuck after Enter recovery — continue to next interval
|
|
1996
|
+
this.logger.debug('Still stuck after Enter recovery, continuing verification', {
|
|
1997
|
+
sessionName,
|
|
1998
|
+
attempt,
|
|
1999
|
+
});
|
|
2000
|
+
await delay(intervalMs);
|
|
2001
|
+
continue;
|
|
2002
|
+
}
|
|
2003
|
+
// Prompt gone AND no stuck text → agent started processing
|
|
2004
|
+
this.logger.debug('Prompt gone, no stuck text — message delivered', {
|
|
2005
|
+
sessionName,
|
|
2006
|
+
attempt,
|
|
2007
|
+
});
|
|
2008
|
+
claudeDelivered = true;
|
|
2009
|
+
break;
|
|
2010
|
+
}
|
|
2011
|
+
if (claudeDelivered) {
|
|
2012
|
+
return true;
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
else {
|
|
2016
|
+
// --- Phase 1: Direct stuck-at-prompt detection ---
|
|
2017
|
+
// Check if our message text is literally sitting on the `> ` prompt
|
|
2018
|
+
// line. This is the definitive signal that Enter was not pressed or
|
|
2019
|
+
// was silently consumed by the TUI. Unlike output-change detection,
|
|
2020
|
+
// this has no false positives from TUI redraws or historical text.
|
|
2021
|
+
const stuckAtPrompt = this.isTextStuckAtTuiPrompt(sessionName, message);
|
|
2022
|
+
if (stuckAtPrompt) {
|
|
2023
|
+
// Attempt immediate recovery: press Enter to submit the text
|
|
2024
|
+
const recovered = await this.recoverStuckTuiMessage(sessionName, message);
|
|
2025
|
+
if (recovered) {
|
|
2026
|
+
this.logger.info('Message recovered via Enter re-press', {
|
|
2027
|
+
sessionName,
|
|
2028
|
+
attempt,
|
|
2029
|
+
});
|
|
2030
|
+
return true;
|
|
2031
|
+
}
|
|
2032
|
+
// Still stuck after recovery — fall through to retry with cleanup
|
|
2033
|
+
this.logger.warn('Message still stuck after Enter recovery, will retry', {
|
|
2034
|
+
sessionName,
|
|
2035
|
+
attempt,
|
|
2036
|
+
});
|
|
2037
|
+
// Skip the output-change check — we know it's stuck
|
|
2038
|
+
}
|
|
2039
|
+
else {
|
|
2040
|
+
// --- Phase 2: Output-change detection ---
|
|
2041
|
+
// Text is NOT on the prompt line. Either it was delivered (Enter
|
|
2042
|
+
// worked) or it was never written (TUI was defocused and the Ink
|
|
2043
|
+
// framework consumed the text silently). Compare output snapshots.
|
|
2044
|
+
const afterOutput = sessionHelper.capturePane(sessionName, 20);
|
|
2045
|
+
const lengthDiff = afterOutput.length - beforeLength;
|
|
2046
|
+
const contentChanged = beforeOutput !== afterOutput;
|
|
2047
|
+
// CRITICAL: Only check for processing indicators in NEW content
|
|
2048
|
+
// (the diff between before and after). Checking the full afterOutput
|
|
2049
|
+
// causes false positives because words like "generating", "searching"
|
|
2050
|
+
// commonly appear in the agent's previous response text visible in
|
|
2051
|
+
// the 20-line capture.
|
|
2052
|
+
let newContent = '';
|
|
2053
|
+
if (contentChanged && afterOutput.length > beforeOutput.length) {
|
|
2054
|
+
newContent = afterOutput.slice(beforeLength);
|
|
2055
|
+
}
|
|
2056
|
+
else if (contentChanged) {
|
|
2057
|
+
const beforeLines = new Set(beforeOutput.split('\n'));
|
|
2058
|
+
newContent = afterOutput
|
|
2059
|
+
.split('\n')
|
|
2060
|
+
.filter((line) => !beforeLines.has(line))
|
|
2061
|
+
.join('\n');
|
|
2062
|
+
}
|
|
2063
|
+
const hasProcessingIndicators = TERMINAL_PATTERNS.PROCESSING_WITH_TEXT.test(newContent || afterOutput.slice(-500));
|
|
2064
|
+
const hasGeminiIndicators = newContent.length > 0
|
|
2065
|
+
&& /reading|thinking|processing|analyzing|generating|searching/i.test(newContent);
|
|
2066
|
+
const significantLengthChange = Math.abs(lengthDiff) > 10;
|
|
2067
|
+
const delivered = (lengthDiff > 20)
|
|
2068
|
+
|| (contentChanged && significantLengthChange)
|
|
2069
|
+
|| hasProcessingIndicators
|
|
2070
|
+
|| hasGeminiIndicators;
|
|
2071
|
+
if (delivered) {
|
|
2072
|
+
this.logger.debug('Message delivered successfully (TUI output changed)', {
|
|
2073
|
+
sessionName,
|
|
2074
|
+
attempt,
|
|
2075
|
+
lengthDiff,
|
|
2076
|
+
contentChanged,
|
|
2077
|
+
hasProcessingIndicators,
|
|
2078
|
+
hasGeminiIndicators,
|
|
2079
|
+
newContentLength: newContent.length,
|
|
2080
|
+
});
|
|
2081
|
+
return true;
|
|
2082
|
+
}
|
|
2083
|
+
this.logger.warn('TUI output did not change after send — message may not have been accepted', {
|
|
2084
|
+
sessionName,
|
|
2085
|
+
attempt,
|
|
2086
|
+
lengthDiff,
|
|
2087
|
+
contentChanged,
|
|
2088
|
+
hasProcessingIndicators,
|
|
2089
|
+
hasGeminiIndicators,
|
|
2090
|
+
newContentLength: newContent.length,
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
// Message stuck at prompt — clear line and retry.
|
|
2095
|
+
this.logger.warn('Message stuck at prompt after send, clearing for retry', {
|
|
2096
|
+
sessionName,
|
|
2097
|
+
attempt,
|
|
2098
|
+
});
|
|
2099
|
+
if (isClaudeCode) {
|
|
2100
|
+
await sessionHelper.clearCurrentCommandLine(sessionName);
|
|
2101
|
+
}
|
|
2102
|
+
else {
|
|
2103
|
+
// Gemini CLI retry cleanup: be careful with cleanup keystrokes.
|
|
2104
|
+
// If output changed (contentChanged=true), text likely reached the
|
|
2105
|
+
// input box but wasn't submitted — Ctrl+C safely clears it.
|
|
2106
|
+
// If output did NOT change at all, the TUI input is likely defocused
|
|
2107
|
+
// and the input box is EMPTY. Ctrl+C on an empty Gemini CLI prompt
|
|
2108
|
+
// triggers /quit and exits the CLI entirely.
|
|
2109
|
+
const noOutputChange = !isClaudeCode && beforeOutput === sessionHelper.capturePane(sessionName, 20);
|
|
2110
|
+
if (noOutputChange) {
|
|
2111
|
+
this.logger.warn('No output change detected — TUI input likely defocused, using Tab to cycle Ink focus', {
|
|
2112
|
+
sessionName,
|
|
2113
|
+
attempt,
|
|
2114
|
+
});
|
|
2115
|
+
// Tab triggers focusNext() in Ink's FocusContext, which should
|
|
2116
|
+
// cycle focus back to the InputPrompt component even when it's
|
|
2117
|
+
// defocused. This is more reliable than Enter (which is silently
|
|
2118
|
+
// consumed when no component is focused).
|
|
2119
|
+
await sessionHelper.sendKey(sessionName, 'Tab');
|
|
2120
|
+
await delay(300);
|
|
2121
|
+
await sessionHelper.sendEnter(sessionName);
|
|
2122
|
+
await delay(300);
|
|
2123
|
+
}
|
|
2124
|
+
else {
|
|
2125
|
+
await sessionHelper.sendCtrlC(sessionName);
|
|
2126
|
+
await delay(200);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
await delay(SESSION_COMMAND_DELAYS.CLEAR_COMMAND_DELAY);
|
|
2130
|
+
if (attempt < maxAttempts) {
|
|
2131
|
+
await delay(SESSION_COMMAND_DELAYS.MESSAGE_RETRY_DELAY);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
catch (error) {
|
|
2135
|
+
this.logger.error('Error during message delivery', {
|
|
2136
|
+
sessionName,
|
|
2137
|
+
attempt,
|
|
2138
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2139
|
+
});
|
|
2140
|
+
if (attempt < maxAttempts) {
|
|
2141
|
+
await delay(SESSION_COMMAND_DELAYS.MESSAGE_RETRY_DELAY);
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
this.logger.error('Message delivery failed after all retry attempts', {
|
|
2146
|
+
sessionName,
|
|
2147
|
+
maxAttempts,
|
|
2148
|
+
messageLength: message.length,
|
|
2149
|
+
});
|
|
2150
|
+
return false;
|
|
2151
|
+
}
|
|
2152
|
+
/**
|
|
2153
|
+
* Check if a sent message is stuck at the terminal prompt (Enter was not accepted).
|
|
2154
|
+
*
|
|
2155
|
+
* Captures the current terminal pane and looks for the message text still visible
|
|
2156
|
+
* on the last few lines. If the text is found, Enter was not processed and the
|
|
2157
|
+
* message is stuck.
|
|
2158
|
+
*
|
|
2159
|
+
* @param sessionName - The session to check
|
|
2160
|
+
* @param message - The original message that was sent
|
|
2161
|
+
* @returns true if the message text is still visible at the prompt (stuck)
|
|
2162
|
+
*/
|
|
2163
|
+
async isMessageStuckAtPrompt(sessionName, message) {
|
|
2164
|
+
try {
|
|
2165
|
+
const sessionHelper = await this.getSessionHelper();
|
|
2166
|
+
const output = sessionHelper.capturePane(sessionName);
|
|
2167
|
+
if (!output || output.trim().length === 0) {
|
|
2168
|
+
return false;
|
|
2169
|
+
}
|
|
2170
|
+
// Extract a search token from the message:
|
|
2171
|
+
// Strip [CHAT:uuid] prefix if present, then take the first 40 chars
|
|
2172
|
+
const chatPrefixMatch = message.match(/^\[CHAT:[^\]]+\]\s*/);
|
|
2173
|
+
const contentAfterPrefix = chatPrefixMatch
|
|
2174
|
+
? message.slice(chatPrefixMatch[0].length)
|
|
2175
|
+
: message;
|
|
2176
|
+
const searchToken = contentAfterPrefix.slice(0, 40).trim();
|
|
2177
|
+
// Also use [CHAT: as a secondary token if message has a CHAT prefix
|
|
2178
|
+
const chatToken = chatPrefixMatch ? '[CHAT:' : null;
|
|
2179
|
+
// Check last 20 non-empty lines for either token.
|
|
2180
|
+
// Gemini CLI TUI has status bars at the bottom (branch, sandbox, model info)
|
|
2181
|
+
// that push input content further up. 5 lines was insufficient.
|
|
2182
|
+
const lines = output.split('\n').filter((line) => line.trim().length > 0);
|
|
2183
|
+
const linesToCheck = lines.slice(-20);
|
|
2184
|
+
const isStuck = linesToCheck.some((line) => {
|
|
2185
|
+
// Strip TUI box-drawing borders before checking (Gemini CLI wraps content in │...│)
|
|
2186
|
+
const stripped = line.replace(/^[│┃║|\s]+/, '').replace(/[│┃║|\s]+$/, '');
|
|
2187
|
+
if (searchToken && (line.includes(searchToken) || stripped.includes(searchToken)))
|
|
2188
|
+
return true;
|
|
2189
|
+
if (chatToken && (line.includes(chatToken) || stripped.includes(chatToken)))
|
|
2190
|
+
return true;
|
|
2191
|
+
return false;
|
|
2192
|
+
});
|
|
2193
|
+
this.logger.debug('isMessageStuckAtPrompt result', {
|
|
2194
|
+
sessionName,
|
|
2195
|
+
isStuck,
|
|
2196
|
+
searchToken: searchToken.slice(0, 20),
|
|
2197
|
+
linesChecked: linesToCheck.length,
|
|
2198
|
+
});
|
|
2199
|
+
return isStuck;
|
|
2200
|
+
}
|
|
2201
|
+
catch (error) {
|
|
2202
|
+
this.logger.warn('Error checking if message stuck at prompt', {
|
|
2203
|
+
sessionName,
|
|
2204
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2205
|
+
});
|
|
2206
|
+
return false;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Check if message text is stuck at the TUI prompt line (Enter was not pressed).
|
|
2211
|
+
*
|
|
2212
|
+
* Unlike `isMessageStuckAtPrompt` which checks the last 20 lines for the message
|
|
2213
|
+
* text ANYWHERE, this method specifically checks whether the message text appears
|
|
2214
|
+
* ON the prompt line itself (the line starting with `> ` or `│ > `).
|
|
2215
|
+
*
|
|
2216
|
+
* After Enter is successfully pressed, the text moves from the prompt line to the
|
|
2217
|
+
* chat history area. So if the text is still on the prompt line, Enter was dropped.
|
|
2218
|
+
*
|
|
2219
|
+
* This avoids the false positive problem where `isMessageStuckAtPrompt` matches
|
|
2220
|
+
* the message text in the TUI's chat history (which echoes submitted messages).
|
|
2221
|
+
*
|
|
2222
|
+
* @param sessionName - The session to check
|
|
2223
|
+
* @param message - The original message that was sent
|
|
2224
|
+
* @returns true if the message text is visible on the TUI prompt line (stuck)
|
|
2225
|
+
*/
|
|
2226
|
+
isTextStuckAtTuiPrompt(sessionName, message) {
|
|
2227
|
+
try {
|
|
2228
|
+
const sessionHelper = this._sessionHelper;
|
|
2229
|
+
if (!sessionHelper)
|
|
2230
|
+
return false;
|
|
2231
|
+
const output = sessionHelper.capturePane(sessionName);
|
|
2232
|
+
if (!output || output.trim().length === 0)
|
|
2233
|
+
return false;
|
|
2234
|
+
// Extract search token: first 30 chars of the message content
|
|
2235
|
+
// (after stripping any [CHAT:uuid] prefix)
|
|
2236
|
+
const chatPrefixMatch = message.match(/^\[CHAT:[^\]]+\]\s*/);
|
|
2237
|
+
const contentAfterPrefix = chatPrefixMatch
|
|
2238
|
+
? message.slice(chatPrefixMatch[0].length)
|
|
2239
|
+
: message;
|
|
2240
|
+
const searchToken = contentAfterPrefix.slice(0, 30).trim();
|
|
2241
|
+
if (!searchToken)
|
|
2242
|
+
return false;
|
|
2243
|
+
// Find lines that look like a TUI prompt with text after it.
|
|
2244
|
+
// Gemini CLI TUI wraps content in box-drawing borders: │ > text │
|
|
2245
|
+
// Or without borders: > text
|
|
2246
|
+
// The prompt line is the line with `> ` followed by actual content.
|
|
2247
|
+
const lines = output.split('\n');
|
|
2248
|
+
const promptLineRegex = /^[│┃║|\s]*>\s+(.+)/;
|
|
2249
|
+
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 25); i--) {
|
|
2250
|
+
const line = lines[i];
|
|
2251
|
+
const match = line.match(promptLineRegex);
|
|
2252
|
+
if (match) {
|
|
2253
|
+
const promptContent = match[1].replace(/[│┃║|\s]+$/, '').trim();
|
|
2254
|
+
// Check if the prompt line content contains our message text
|
|
2255
|
+
if (promptContent.length > 5 && promptContent.includes(searchToken)) {
|
|
2256
|
+
this.logger.warn('Text stuck at TUI prompt — Enter was not pressed', {
|
|
2257
|
+
sessionName,
|
|
2258
|
+
searchToken: searchToken.slice(0, 20),
|
|
2259
|
+
promptContent: promptContent.slice(0, 60),
|
|
2260
|
+
});
|
|
2261
|
+
return true;
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
return false;
|
|
2266
|
+
}
|
|
2267
|
+
catch (error) {
|
|
2268
|
+
this.logger.debug('Error in isTextStuckAtTuiPrompt', {
|
|
2269
|
+
sessionName,
|
|
2270
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2271
|
+
});
|
|
2272
|
+
return false;
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
/**
|
|
2276
|
+
* Attempt to recover from text stuck at TUI prompt by pressing Enter.
|
|
2277
|
+
* Checks if text is stuck, sends Enter, waits, then verifies.
|
|
2278
|
+
*
|
|
2279
|
+
* @param sessionName - The session to recover
|
|
2280
|
+
* @param message - The original message
|
|
2281
|
+
* @returns true if recovery succeeded (text is no longer at prompt)
|
|
2282
|
+
*/
|
|
2283
|
+
async recoverStuckTuiMessage(sessionName, message) {
|
|
2284
|
+
const sessionHelper = await this.getSessionHelper();
|
|
2285
|
+
// Press Enter to submit the stuck text
|
|
2286
|
+
this.logger.info('Pressing Enter to recover stuck TUI message', { sessionName });
|
|
2287
|
+
await sessionHelper.sendEnter(sessionName);
|
|
2288
|
+
await delay(500);
|
|
2289
|
+
// Double-tap: send a backup Enter in case the first was consumed
|
|
2290
|
+
await sessionHelper.sendEnter(sessionName);
|
|
2291
|
+
await delay(2000);
|
|
2292
|
+
// Verify text is no longer at prompt
|
|
2293
|
+
const stillStuck = this.isTextStuckAtTuiPrompt(sessionName, message);
|
|
2294
|
+
if (!stillStuck) {
|
|
2295
|
+
this.logger.info('TUI message recovery succeeded — Enter accepted', { sessionName });
|
|
2296
|
+
return true;
|
|
2297
|
+
}
|
|
2298
|
+
this.logger.warn('TUI message still stuck after Enter recovery', { sessionName });
|
|
2299
|
+
return false;
|
|
2300
|
+
}
|
|
2301
|
+
/**
|
|
2302
|
+
* Start a background scanner that periodically checks all registered TUI
|
|
2303
|
+
* sessions for text stuck at the prompt (Enter not pressed).
|
|
2304
|
+
*
|
|
2305
|
+
* This is a safety net that catches messages missed by the per-send
|
|
2306
|
+
* verification in `sendMessageWithRetry`. It scans every 30 seconds.
|
|
2307
|
+
*
|
|
2308
|
+
* When stuck text is detected, the scanner presses Enter to recover.
|
|
2309
|
+
* If text has been sitting at the prompt for multiple consecutive scans,
|
|
2310
|
+
* it escalates to Tab + Enter (TUI focus recovery).
|
|
2311
|
+
*/
|
|
2312
|
+
startStuckMessageDetector() {
|
|
2313
|
+
if (this.stuckMessageDetectorTimer) {
|
|
2314
|
+
this.logger.debug('Stuck message detector already running');
|
|
2315
|
+
return;
|
|
2316
|
+
}
|
|
2317
|
+
const SCAN_INTERVAL_MS = 30000; // 30 seconds
|
|
2318
|
+
this.logger.info('Starting background stuck-message detector', {
|
|
2319
|
+
intervalMs: SCAN_INTERVAL_MS,
|
|
2320
|
+
});
|
|
2321
|
+
this.stuckMessageDetectorTimer = setInterval(async () => {
|
|
2322
|
+
try {
|
|
2323
|
+
await this.scanForStuckMessages();
|
|
2324
|
+
}
|
|
2325
|
+
catch (error) {
|
|
2326
|
+
this.logger.debug('Stuck message scan error (non-fatal)', {
|
|
2327
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2328
|
+
});
|
|
2329
|
+
}
|
|
2330
|
+
}, SCAN_INTERVAL_MS);
|
|
2331
|
+
}
|
|
2332
|
+
/**
|
|
2333
|
+
* Stop the background stuck-message detector.
|
|
2334
|
+
*/
|
|
2335
|
+
stopStuckMessageDetector() {
|
|
2336
|
+
if (this.stuckMessageDetectorTimer) {
|
|
2337
|
+
clearInterval(this.stuckMessageDetectorTimer);
|
|
2338
|
+
this.stuckMessageDetectorTimer = null;
|
|
2339
|
+
this.logger.info('Stopped background stuck-message detector');
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
/**
|
|
2343
|
+
* Scan all sessions for stuck messages. Two complementary strategies:
|
|
2344
|
+
*
|
|
2345
|
+
* 1. TUI prompt-line scanning (existing): checks registered TUI sessions
|
|
2346
|
+
* for text sitting on the `> ` prompt line.
|
|
2347
|
+
* 2. Tracked-message scanning (new, all runtimes): checks if any recently
|
|
2348
|
+
* sent message text is still visible at the bottom of the terminal
|
|
2349
|
+
* after 15+ seconds — runtime-agnostic Enter-drop detection.
|
|
2350
|
+
*/
|
|
2351
|
+
async scanForStuckMessages() {
|
|
2352
|
+
const sessionHelper = this._sessionHelper;
|
|
2353
|
+
if (!sessionHelper)
|
|
2354
|
+
return;
|
|
2355
|
+
// --- Part 1: Existing TUI prompt-line scanning (unchanged) ---
|
|
2356
|
+
for (const [sessionName] of this.tuiSessionRegistry) {
|
|
2357
|
+
try {
|
|
2358
|
+
// Skip sessions that no longer exist
|
|
2359
|
+
if (!sessionHelper.sessionExists(sessionName)) {
|
|
2360
|
+
this.tuiSessionRegistry.delete(sessionName);
|
|
2361
|
+
continue;
|
|
2362
|
+
}
|
|
2363
|
+
const output = sessionHelper.capturePane(sessionName);
|
|
2364
|
+
if (!output || output.trim().length === 0)
|
|
2365
|
+
continue;
|
|
2366
|
+
// Look for any text sitting on the prompt line
|
|
2367
|
+
const lines = output.split('\n');
|
|
2368
|
+
const promptLineRegex = /^[│┃║|\s]*>\s+(.+)/;
|
|
2369
|
+
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 25); i--) {
|
|
2370
|
+
const match = lines[i].match(promptLineRegex);
|
|
2371
|
+
if (match) {
|
|
2372
|
+
const promptContent = match[1].replace(/[│┃║|\s]+$/, '').trim();
|
|
2373
|
+
// Only act on substantial text (> 10 chars) to avoid false positives
|
|
2374
|
+
// from TUI rendering artifacts or short status text
|
|
2375
|
+
if (promptContent.length > 10) {
|
|
2376
|
+
// Skip known Gemini CLI idle placeholder text that sits at
|
|
2377
|
+
// the `> ` prompt when no user input is present. These are
|
|
2378
|
+
// NOT stuck messages — they are TUI decoration.
|
|
2379
|
+
const isPlaceholder = /^Type your message/i.test(promptContent) ||
|
|
2380
|
+
/^@[\w/.]+/.test(promptContent); // e.g., "@path/to/file"
|
|
2381
|
+
if (isPlaceholder) {
|
|
2382
|
+
break;
|
|
2383
|
+
}
|
|
2384
|
+
this.logger.warn('Background scan: text stuck at TUI prompt, pressing Enter', {
|
|
2385
|
+
sessionName,
|
|
2386
|
+
promptContent: promptContent.slice(0, 80),
|
|
2387
|
+
});
|
|
2388
|
+
// Press Enter to submit the stuck text
|
|
2389
|
+
await sessionHelper.sendEnter(sessionName);
|
|
2390
|
+
await delay(500);
|
|
2391
|
+
// Backup Enter
|
|
2392
|
+
await sessionHelper.sendEnter(sessionName);
|
|
2393
|
+
}
|
|
2394
|
+
break; // Only check the first prompt line found from the bottom
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
catch (error) {
|
|
2399
|
+
this.logger.debug('Error scanning session for stuck messages', {
|
|
2400
|
+
sessionName,
|
|
2401
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2402
|
+
});
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
// --- Part 2: Tracked-message scanning (all runtimes) ---
|
|
2406
|
+
const now = Date.now();
|
|
2407
|
+
const MIN_AGE_MS = 15000; // Only check messages older than 15s
|
|
2408
|
+
for (const [sessionName, entries] of this.sentMessageTracker) {
|
|
2409
|
+
if (!sessionHelper.sessionExists(sessionName)) {
|
|
2410
|
+
this.sentMessageTracker.delete(sessionName);
|
|
2411
|
+
continue;
|
|
2412
|
+
}
|
|
2413
|
+
try {
|
|
2414
|
+
const output = sessionHelper.capturePane(sessionName);
|
|
2415
|
+
if (!output || output.trim().length === 0)
|
|
2416
|
+
continue;
|
|
2417
|
+
const bottomText = output.split('\n').slice(-15).join(' ').replace(/\s+/g, ' ');
|
|
2418
|
+
for (const entry of entries) {
|
|
2419
|
+
if (entry.recovered)
|
|
2420
|
+
continue;
|
|
2421
|
+
if (now - entry.sentAt < MIN_AGE_MS)
|
|
2422
|
+
continue;
|
|
2423
|
+
if (bottomText.includes(entry.snippet)) {
|
|
2424
|
+
this.logger.warn('Background scan: tracked message stuck, pressing Enter', {
|
|
2425
|
+
sessionName,
|
|
2426
|
+
snippet: entry.snippet.slice(0, 50),
|
|
2427
|
+
ageMs: now - entry.sentAt,
|
|
2428
|
+
});
|
|
2429
|
+
await sessionHelper.sendEnter(sessionName);
|
|
2430
|
+
await delay(500);
|
|
2431
|
+
await sessionHelper.sendEnter(sessionName); // backup
|
|
2432
|
+
entry.recovered = true;
|
|
2433
|
+
break; // One recovery per session per scan cycle
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
catch (error) {
|
|
2438
|
+
this.logger.debug('Error scanning tracked messages for session', {
|
|
2439
|
+
sessionName,
|
|
2440
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
// Clean up old tracked-message entries
|
|
2445
|
+
for (const [sessionName, entries] of this.sentMessageTracker) {
|
|
2446
|
+
const fresh = entries.filter(e => now - e.sentAt < 5 * 60 * 1000);
|
|
2447
|
+
if (fresh.length === 0) {
|
|
2448
|
+
this.sentMessageTracker.delete(sessionName);
|
|
2449
|
+
}
|
|
2450
|
+
else {
|
|
2451
|
+
this.sentMessageTracker.set(sessionName, fresh);
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
/**
|
|
2456
|
+
* Unregister a TUI session from the stuck-message detector.
|
|
2457
|
+
* Call this when a session is destroyed or agent is stopped.
|
|
2458
|
+
*
|
|
2459
|
+
* @param sessionName - The session to unregister
|
|
2460
|
+
*/
|
|
2461
|
+
unregisterTuiSession(sessionName) {
|
|
2462
|
+
this.tuiSessionRegistry.delete(sessionName);
|
|
2463
|
+
this.sentMessageTracker.delete(sessionName);
|
|
2464
|
+
}
|
|
2465
|
+
/**
|
|
2466
|
+
* Register a message that was sent to a session for background
|
|
2467
|
+
* stuck-detection. The scanner will periodically check if the
|
|
2468
|
+
* message text is still visible at the bottom of the terminal.
|
|
2469
|
+
*
|
|
2470
|
+
* @param sessionName - The session the message was sent to
|
|
2471
|
+
* @param message - The full message text
|
|
2472
|
+
*/
|
|
2473
|
+
trackSentMessage(sessionName, message) {
|
|
2474
|
+
// Extract a search snippet: skip [CHAT:uuid] prefix, take first 80 chars
|
|
2475
|
+
const prefixMatch = message.match(/^\[CHAT:[^\]]+\]\s*/);
|
|
2476
|
+
const contentStart = prefixMatch ? prefixMatch[0].length : 0;
|
|
2477
|
+
// Normalize whitespace: messages may contain \n from enhanced templates.
|
|
2478
|
+
// Terminal bottom text is join(' '), so \n in snippet would never match.
|
|
2479
|
+
const snippet = message.slice(contentStart, contentStart + 80).replace(/\s+/g, ' ').trim();
|
|
2480
|
+
if (snippet.length < 10)
|
|
2481
|
+
return; // Too short to reliably match
|
|
2482
|
+
const entries = this.sentMessageTracker.get(sessionName) || [];
|
|
2483
|
+
entries.push({ snippet, sentAt: Date.now(), recovered: false });
|
|
2484
|
+
// Keep only last 5 minutes of entries
|
|
2485
|
+
const cutoff = Date.now() - 5 * 60 * 1000;
|
|
2486
|
+
this.sentMessageTracker.set(sessionName, entries.filter(e => e.sentAt > cutoff));
|
|
2487
|
+
// Ensure the background scanner is running
|
|
2488
|
+
this.startStuckMessageDetector();
|
|
2489
|
+
}
|
|
2490
|
+
/**
|
|
2491
|
+
* Check if Claude Code appears to be at an input prompt.
|
|
2492
|
+
* Looks for common prompt indicators in terminal output.
|
|
2493
|
+
*
|
|
2494
|
+
* @param terminalOutput - The terminal output to check
|
|
2495
|
+
* @returns true if Claude Code appears to be at a prompt
|
|
2496
|
+
*/
|
|
2497
|
+
isClaudeAtPrompt(terminalOutput, runtimeType) {
|
|
2498
|
+
// Handle null/undefined/empty input gracefully
|
|
2499
|
+
if (!terminalOutput || typeof terminalOutput !== 'string') {
|
|
2500
|
+
this.logger.debug('Terminal output is empty or invalid, assuming at prompt');
|
|
2501
|
+
return true; // Assume at prompt if no output (safer for message delivery)
|
|
2502
|
+
}
|
|
2503
|
+
// Only analyze the tail of the buffer to avoid matching historical prompts
|
|
2504
|
+
const tailSection = terminalOutput.slice(-2000);
|
|
2505
|
+
const isGemini = runtimeType === RUNTIME_TYPES.GEMINI_CLI;
|
|
2506
|
+
const isClaudeCode = runtimeType === RUNTIME_TYPES.CLAUDE_CODE;
|
|
2507
|
+
// Use runtime-specific regex when runtime is known, combined pattern otherwise
|
|
2508
|
+
const streamPattern = isClaudeCode
|
|
2509
|
+
? TERMINAL_PATTERNS.CLAUDE_CODE_PROMPT
|
|
2510
|
+
: isGemini
|
|
2511
|
+
? TERMINAL_PATTERNS.GEMINI_CLI_PROMPT
|
|
2512
|
+
: TERMINAL_PATTERNS.PROMPT_STREAM;
|
|
2513
|
+
// Check for prompt FIRST. Processing indicators like "thinking" or "analyzing"
|
|
2514
|
+
// can appear in the agent's previous response text and persist in the terminal
|
|
2515
|
+
// scroll buffer, causing false negatives if checked before the prompt.
|
|
2516
|
+
if (streamPattern.test(tailSection)) {
|
|
2517
|
+
return true;
|
|
2518
|
+
}
|
|
2519
|
+
// Fallback: check last several lines for prompt indicators.
|
|
2520
|
+
// The prompt may not be on the very last line due to status bars,
|
|
2521
|
+
// notifications, or terminal wrapping below the prompt.
|
|
2522
|
+
const lines = tailSection.split('\n').filter((line) => line.trim().length > 0);
|
|
2523
|
+
const linesToCheck = lines.slice(-10);
|
|
2524
|
+
const hasPrompt = linesToCheck.some((line) => {
|
|
2525
|
+
const trimmed = line.trim();
|
|
2526
|
+
// Strip TUI box-drawing borders (│, ┃, etc.) that Gemini CLI wraps around prompts
|
|
2527
|
+
const stripped = trimmed.replace(/^[│┃|]+\s*/, '').replace(/\s*[│┃|]+$/, '');
|
|
2528
|
+
// Claude Code prompts: ❯, ⏵, $ alone on a line
|
|
2529
|
+
if (!isGemini) {
|
|
2530
|
+
if (['❯', '⏵', '$'].some(ch => trimmed === ch || stripped === ch)) {
|
|
2531
|
+
return true;
|
|
2532
|
+
}
|
|
2533
|
+
// ❯❯ = bypass permissions prompt (idle).
|
|
2534
|
+
// Matches "❯❯", "❯❯ ", and "❯❯ bypass permissions on (shift+tab to cycle)".
|
|
2535
|
+
// This line disappears during processing and reappears when idle.
|
|
2536
|
+
if (trimmed.startsWith('❯❯')) {
|
|
2537
|
+
return true;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
// Gemini CLI prompts: > or ! followed by space
|
|
2541
|
+
if (!isClaudeCode) {
|
|
2542
|
+
if (trimmed.startsWith('> ') || trimmed.startsWith('! ') ||
|
|
2543
|
+
stripped.startsWith('> ') || stripped.startsWith('! ')) {
|
|
2544
|
+
return true;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
return false;
|
|
2548
|
+
});
|
|
2549
|
+
if (hasPrompt) {
|
|
2550
|
+
return true;
|
|
2551
|
+
}
|
|
2552
|
+
// No prompt found — check if still processing. Only check the last few
|
|
2553
|
+
// lines to avoid matching words like "thinking" in historical response text.
|
|
2554
|
+
const recentLines = linesToCheck.slice(-5).join('\n');
|
|
2555
|
+
if (TERMINAL_PATTERNS.PROCESSING_WITH_TEXT.test(recentLines)) {
|
|
2556
|
+
this.logger.debug('Processing indicators present near bottom of output');
|
|
2557
|
+
return false;
|
|
2558
|
+
}
|
|
2559
|
+
return false;
|
|
2560
|
+
}
|
|
2561
|
+
/**
|
|
2562
|
+
* Detect if Gemini CLI is currently in shell mode.
|
|
2563
|
+
*
|
|
2564
|
+
* In shell mode, Gemini CLI changes its prompt from `>` to `!`. Any input
|
|
2565
|
+
* sent in this mode is executed as a shell command instead of being passed
|
|
2566
|
+
* to the model. This method examines the last few lines of terminal output
|
|
2567
|
+
* for shell mode prompt indicators.
|
|
2568
|
+
*
|
|
2569
|
+
* @param terminalOutput - Captured terminal pane content
|
|
2570
|
+
* @returns true if the terminal shows a shell mode prompt
|
|
2571
|
+
*/
|
|
2572
|
+
isGeminiInShellMode(terminalOutput) {
|
|
2573
|
+
if (!terminalOutput || typeof terminalOutput !== 'string') {
|
|
2574
|
+
return false;
|
|
2575
|
+
}
|
|
2576
|
+
const lines = terminalOutput.split('\n').filter((line) => line.trim().length > 0);
|
|
2577
|
+
const linesToCheck = lines.slice(-10);
|
|
2578
|
+
return linesToCheck.some((line) => {
|
|
2579
|
+
const trimmed = line.trim();
|
|
2580
|
+
// Strip TUI box-drawing borders
|
|
2581
|
+
const stripped = trimmed.replace(/^[│┃|]+\s*/, '').replace(/\s*[│┃|]+$/, '');
|
|
2582
|
+
// Shell mode prompt: `!` alone or `! ` with text (not `> ` which is normal mode)
|
|
2583
|
+
// Check stripped line — after removing box-drawing, if it starts with `! ` or equals `!`
|
|
2584
|
+
if (stripped === '!' || stripped.startsWith('! ')) {
|
|
2585
|
+
return true;
|
|
2586
|
+
}
|
|
2587
|
+
// Also check pattern-based detection for bordered prompts
|
|
2588
|
+
return GEMINI_SHELL_MODE_CONSTANTS.SHELL_MODE_PROMPT_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
/**
|
|
2592
|
+
* Escape from Gemini CLI shell mode by sending Escape key.
|
|
2593
|
+
*
|
|
2594
|
+
* Sends Escape and waits for the prompt to change from `!` back to `>`.
|
|
2595
|
+
* Retries up to MAX_ESCAPE_ATTEMPTS times.
|
|
2596
|
+
*
|
|
2597
|
+
* @param sessionName - The session running Gemini CLI
|
|
2598
|
+
* @param sessionHelper - SessionCommandHelper instance
|
|
2599
|
+
* @returns true if successfully escaped shell mode, false if still in shell mode
|
|
2600
|
+
*/
|
|
2601
|
+
async escapeGeminiShellMode(sessionName, sessionHelper) {
|
|
2602
|
+
for (let attempt = 1; attempt <= GEMINI_SHELL_MODE_CONSTANTS.MAX_ESCAPE_ATTEMPTS; attempt++) {
|
|
2603
|
+
this.logger.info('Gemini CLI in shell mode, sending Escape to exit', {
|
|
2604
|
+
sessionName,
|
|
2605
|
+
attempt,
|
|
2606
|
+
});
|
|
2607
|
+
await sessionHelper.sendEscape(sessionName);
|
|
2608
|
+
await delay(GEMINI_SHELL_MODE_CONSTANTS.ESCAPE_DELAY_MS);
|
|
2609
|
+
// Check if we're back to normal mode
|
|
2610
|
+
const output = sessionHelper.capturePane(sessionName);
|
|
2611
|
+
if (!this.isGeminiInShellMode(output)) {
|
|
2612
|
+
this.logger.info('Successfully exited Gemini CLI shell mode', {
|
|
2613
|
+
sessionName,
|
|
2614
|
+
attempt,
|
|
2615
|
+
});
|
|
2616
|
+
return true;
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
this.logger.warn('Failed to exit Gemini CLI shell mode after max attempts', {
|
|
2620
|
+
sessionName,
|
|
2621
|
+
maxAttempts: GEMINI_SHELL_MODE_CONSTANTS.MAX_ESCAPE_ATTEMPTS,
|
|
2622
|
+
});
|
|
2623
|
+
return false;
|
|
2624
|
+
}
|
|
2625
|
+
/**
|
|
2626
|
+
* Generic key sending to any agent session
|
|
2627
|
+
* @param sessionName The agent session name
|
|
2628
|
+
* @param key The key to send (e.g., 'Enter', 'Ctrl+C')
|
|
2629
|
+
* @returns Promise with success/error information
|
|
2630
|
+
*/
|
|
2631
|
+
async sendKeyToAgent(sessionName, key) {
|
|
2632
|
+
try {
|
|
2633
|
+
// Get session helper once to avoid repeated async calls
|
|
2634
|
+
const sessionHelper = await this.getSessionHelper();
|
|
2635
|
+
// Check if session exists
|
|
2636
|
+
const sessionExists = sessionHelper.sessionExists(sessionName);
|
|
2637
|
+
if (!sessionExists) {
|
|
2638
|
+
return {
|
|
2639
|
+
success: false,
|
|
2640
|
+
error: `Session '${sessionName}' does not exist`,
|
|
2641
|
+
};
|
|
2642
|
+
}
|
|
2643
|
+
// Send key using session command helper
|
|
2644
|
+
await sessionHelper.sendKey(sessionName, key);
|
|
2645
|
+
this.logger.info('Key sent to agent successfully', {
|
|
2646
|
+
sessionName,
|
|
2647
|
+
key,
|
|
2648
|
+
});
|
|
2649
|
+
return {
|
|
2650
|
+
success: true,
|
|
2651
|
+
message: `${key} key sent to agent successfully`,
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2654
|
+
catch (error) {
|
|
2655
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2656
|
+
this.logger.error('Failed to send key to agent', {
|
|
2657
|
+
sessionName,
|
|
2658
|
+
key,
|
|
2659
|
+
error: errorMessage,
|
|
2660
|
+
});
|
|
2661
|
+
return {
|
|
2662
|
+
success: false,
|
|
2663
|
+
error: errorMessage,
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
/**
|
|
2668
|
+
* Generic health check for any agent session
|
|
2669
|
+
* @param sessionName The agent session name
|
|
2670
|
+
* @param role The agent role for additional context
|
|
2671
|
+
* @param timeout Timeout for health check in milliseconds
|
|
2672
|
+
* @returns Promise with health status information
|
|
2673
|
+
*/
|
|
2674
|
+
async checkAgentHealth(sessionName, role, timeout = 1000) {
|
|
2675
|
+
try {
|
|
2676
|
+
// Lightweight health check with timeout
|
|
2677
|
+
const agentRunning = await Promise.race([
|
|
2678
|
+
(await this.getSessionHelper()).sessionExists(sessionName),
|
|
2679
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Health check timeout')), timeout)),
|
|
2680
|
+
]).catch(() => false);
|
|
2681
|
+
return {
|
|
2682
|
+
success: true,
|
|
2683
|
+
data: {
|
|
2684
|
+
agent: {
|
|
2685
|
+
sessionName,
|
|
2686
|
+
role,
|
|
2687
|
+
running: agentRunning,
|
|
2688
|
+
status: agentRunning
|
|
2689
|
+
? CREWLY_CONSTANTS.AGENT_STATUSES.ACTIVE
|
|
2690
|
+
: CREWLY_CONSTANTS.AGENT_STATUSES.INACTIVE,
|
|
2691
|
+
},
|
|
2692
|
+
timestamp: new Date().toISOString(),
|
|
2693
|
+
},
|
|
2694
|
+
};
|
|
2695
|
+
}
|
|
2696
|
+
catch (error) {
|
|
2697
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2698
|
+
this.logger.error('Failed to check agent health', {
|
|
2699
|
+
sessionName,
|
|
2700
|
+
role,
|
|
2701
|
+
error: errorMessage,
|
|
2702
|
+
});
|
|
2703
|
+
return {
|
|
2704
|
+
success: false,
|
|
2705
|
+
error: errorMessage,
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
/**
|
|
2710
|
+
* Write the prompt to a file and send a short instruction to the agent to read it.
|
|
2711
|
+
*
|
|
2712
|
+
* Instead of pasting large multi-line prompts directly into the terminal (which
|
|
2713
|
+
* causes bracketed paste issues, shell interpretation errors, and truncation),
|
|
2714
|
+
* we write the prompt to ~/.crewly/prompts/{sessionName}-init.md and send a
|
|
2715
|
+
* single-line instruction telling the agent to read that file.
|
|
2716
|
+
*
|
|
2717
|
+
* @param sessionName The session name
|
|
2718
|
+
* @param prompt The full system prompt to deliver
|
|
2719
|
+
* @param runtimeType The agent runtime type
|
|
2720
|
+
* @param abortSignal Optional signal to cancel the operation (e.g. on runtime exit)
|
|
2721
|
+
* @returns true if the instruction was delivered successfully
|
|
2722
|
+
*/
|
|
2723
|
+
async sendPromptRobustly(sessionName, prompt, runtimeType = RUNTIME_TYPES.CLAUDE_CODE, abortSignal) {
|
|
2724
|
+
const isClaudeCode = runtimeType === RUNTIME_TYPES.CLAUDE_CODE;
|
|
2725
|
+
const maxAttempts = isClaudeCode ? 1 : 3;
|
|
2726
|
+
const sessionHelper = await this.getSessionHelper();
|
|
2727
|
+
// Step 1: Write prompt to a file.
|
|
2728
|
+
// Claude Code: write to ~/.crewly/prompts/ (always accessible).
|
|
2729
|
+
// Gemini CLI / other TUI runtimes: write INSIDE the project directory
|
|
2730
|
+
// so the file is within the workspace allowlist. Gemini CLI restricts
|
|
2731
|
+
// file reads to workspace directories, and the /directory add command
|
|
2732
|
+
// to add ~/.crewly may fail (e.g., auto-update notification
|
|
2733
|
+
// interferes during postInitialize).
|
|
2734
|
+
const promptsDir = isClaudeCode
|
|
2735
|
+
? path.join(os.homedir(), CREWLY_CONSTANTS.PATHS.CREWLY_HOME, 'prompts')
|
|
2736
|
+
: path.join(this.projectRoot, '.crewly', 'prompts');
|
|
2737
|
+
const promptFilePath = path.join(promptsDir, `${sessionName}-init.md`);
|
|
2738
|
+
try {
|
|
2739
|
+
await mkdir(promptsDir, { recursive: true });
|
|
2740
|
+
await writeFile(promptFilePath, prompt, 'utf8');
|
|
2741
|
+
this.logger.debug('Wrote init prompt to file', {
|
|
2742
|
+
sessionName,
|
|
2743
|
+
promptFilePath,
|
|
2744
|
+
promptLength: prompt.length,
|
|
2745
|
+
});
|
|
2746
|
+
}
|
|
2747
|
+
catch (error) {
|
|
2748
|
+
this.logger.error('Failed to write init prompt file', {
|
|
2749
|
+
sessionName,
|
|
2750
|
+
promptFilePath,
|
|
2751
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2752
|
+
});
|
|
2753
|
+
return false;
|
|
2754
|
+
}
|
|
2755
|
+
// Step 2: Send a short instruction to read the file
|
|
2756
|
+
const instruction = `Read the file at ${promptFilePath} and follow all instructions in it.`;
|
|
2757
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2758
|
+
// Check abort before each attempt
|
|
2759
|
+
if (abortSignal?.aborted) {
|
|
2760
|
+
this.logger.info('Prompt delivery aborted (runtime exited)', { sessionName, attempt });
|
|
2761
|
+
return false;
|
|
2762
|
+
}
|
|
2763
|
+
try {
|
|
2764
|
+
this.logger.debug('Sending file-based prompt instruction', {
|
|
2765
|
+
sessionName,
|
|
2766
|
+
attempt,
|
|
2767
|
+
runtimeType,
|
|
2768
|
+
promptFilePath,
|
|
2769
|
+
});
|
|
2770
|
+
// Capture state before sending
|
|
2771
|
+
const beforeOutput = sessionHelper.capturePane(sessionName, 10);
|
|
2772
|
+
const beforeLength = beforeOutput.length;
|
|
2773
|
+
// Check abort before sending to terminal
|
|
2774
|
+
if (abortSignal?.aborted) {
|
|
2775
|
+
this.logger.info('Prompt delivery aborted before send (runtime exited)', { sessionName });
|
|
2776
|
+
return false;
|
|
2777
|
+
}
|
|
2778
|
+
// Clear any pending input before sending the instruction.
|
|
2779
|
+
// Claude Code: Escape closes slash menus + Ctrl+U clears line.
|
|
2780
|
+
// Gemini CLI (Ink TUI): Do NOT send any cleanup keystrokes.
|
|
2781
|
+
// - Escape defocuses the Ink TUI input permanently (no recovery).
|
|
2782
|
+
// - Ctrl+C at empty prompt triggers /quit and exits the CLI.
|
|
2783
|
+
// - Ctrl+U is ignored by the TUI's custom key handling.
|
|
2784
|
+
// - Shift+Tab toggles safety modes, not focus.
|
|
2785
|
+
// The prompt should be clean at this point (just initialized or
|
|
2786
|
+
// addProjectToAllowlist just ran without defocusing).
|
|
2787
|
+
if (isClaudeCode) {
|
|
2788
|
+
await sessionHelper.sendEscape(sessionName);
|
|
2789
|
+
await delay(200);
|
|
2790
|
+
await sessionHelper.sendKey(sessionName, 'C-u');
|
|
2791
|
+
await delay(300);
|
|
2792
|
+
}
|
|
2793
|
+
// Check abort right before writing instruction to terminal
|
|
2794
|
+
if (abortSignal?.aborted) {
|
|
2795
|
+
this.logger.info('Prompt delivery aborted before instruction send (runtime exited)', { sessionName });
|
|
2796
|
+
return false;
|
|
2797
|
+
}
|
|
2798
|
+
// Send the short instruction
|
|
2799
|
+
await sessionHelper.sendMessage(sessionName, instruction);
|
|
2800
|
+
if (isClaudeCode) {
|
|
2801
|
+
// Claude Code may need an extra Enter after bracketed paste
|
|
2802
|
+
await delay(1000);
|
|
2803
|
+
if (abortSignal?.aborted)
|
|
2804
|
+
return false;
|
|
2805
|
+
await sessionHelper.sendEnter(sessionName);
|
|
2806
|
+
}
|
|
2807
|
+
// Wait for agent to start processing
|
|
2808
|
+
await delay(3000);
|
|
2809
|
+
if (abortSignal?.aborted)
|
|
2810
|
+
return false;
|
|
2811
|
+
// Verify delivery
|
|
2812
|
+
const afterOutput = sessionHelper.capturePane(sessionName, 20);
|
|
2813
|
+
const afterLength = afterOutput.length;
|
|
2814
|
+
const lengthIncrease = afterLength - beforeLength;
|
|
2815
|
+
const hasProcessingIndicators = /thinking|processing|analyzing|registering|reading/i.test(afterOutput);
|
|
2816
|
+
// For Claude Code, also check if the prompt disappeared (meaning
|
|
2817
|
+
// Claude accepted the input and is processing). Claude's UI often
|
|
2818
|
+
// collapses/redraws, making the output shorter — a negative
|
|
2819
|
+
// lengthIncrease does NOT mean delivery failed.
|
|
2820
|
+
const promptGone = isClaudeCode
|
|
2821
|
+
&& !this.isClaudeAtPrompt(sessionHelper.capturePane(sessionName), RUNTIME_TYPES.CLAUDE_CODE);
|
|
2822
|
+
if (lengthIncrease > 20 || hasProcessingIndicators || promptGone) {
|
|
2823
|
+
this.logger.debug('Prompt instruction delivered successfully', {
|
|
2824
|
+
sessionName,
|
|
2825
|
+
attempt,
|
|
2826
|
+
lengthIncrease,
|
|
2827
|
+
hasProcessingIndicators,
|
|
2828
|
+
promptGone,
|
|
2829
|
+
runtimeType,
|
|
2830
|
+
});
|
|
2831
|
+
return true;
|
|
2832
|
+
}
|
|
2833
|
+
this.logger.warn('Prompt instruction delivery may have failed - retrying', {
|
|
2834
|
+
sessionName,
|
|
2835
|
+
attempt,
|
|
2836
|
+
lengthIncrease,
|
|
2837
|
+
runtimeType,
|
|
2838
|
+
});
|
|
2839
|
+
if (attempt < maxAttempts) {
|
|
2840
|
+
await delay(1000);
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
catch (error) {
|
|
2844
|
+
this.logger.error('Error during prompt instruction delivery', {
|
|
2845
|
+
sessionName,
|
|
2846
|
+
attempt,
|
|
2847
|
+
runtimeType,
|
|
2848
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2849
|
+
});
|
|
2850
|
+
if (attempt === maxAttempts) {
|
|
2851
|
+
return false;
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
this.logger.error('Failed to deliver prompt instruction after all attempts', {
|
|
2856
|
+
sessionName,
|
|
2857
|
+
maxAttempts,
|
|
2858
|
+
runtimeType,
|
|
2859
|
+
});
|
|
2860
|
+
return false;
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
//# sourceMappingURL=agent-registration.service.js.map
|