crewly 1.0.12 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/config/constants.ts +6 -0
  2. package/config/roles/_common/memory-instructions.md +10 -1
  3. package/config/roles/architect/prompt.md +7 -0
  4. package/config/roles/backend-developer/prompt.md +7 -0
  5. package/config/roles/content-strategist/prompt.md +69 -0
  6. package/config/roles/designer/prompt.md +7 -0
  7. package/config/roles/developer/prompt.md +7 -0
  8. package/config/roles/frontend-developer/prompt.md +7 -0
  9. package/config/roles/fullstack-dev/prompt.md +7 -0
  10. package/config/roles/generalist/prompt.md +8 -1
  11. package/config/roles/orchestrator/prompt.md +16 -1
  12. package/config/roles/orchestrator/self-evolution.md +72 -0
  13. package/config/roles/product-manager/prompt.md +7 -0
  14. package/config/roles/qa/prompt.md +7 -0
  15. package/config/roles/qa-engineer/prompt.md +7 -0
  16. package/config/roles/sales/prompt.md +7 -0
  17. package/config/roles/support/prompt.md +7 -0
  18. package/config/roles/tpm/prompt.md +7 -0
  19. package/config/skills/agent/core/register-self/instructions.md +1 -1
  20. package/config/skills/agent/core/remember/instructions.md +3 -3
  21. package/config/skills/orchestrator/delegate-task/execute.sh +21 -1
  22. package/config/skills/orchestrator/delegate-task/instructions.md +1 -0
  23. package/config/skills/orchestrator/read-session-logs/execute.sh +14 -0
  24. package/config/skills/orchestrator/read-session-logs/instructions.md +33 -0
  25. package/config/skills/orchestrator/read-session-logs/skill.json +20 -0
  26. package/config/skills/orchestrator/read-system-logs/execute.sh +18 -0
  27. package/config/skills/orchestrator/read-system-logs/instructions.md +30 -0
  28. package/config/skills/orchestrator/read-system-logs/skill.json +20 -0
  29. package/config/skills/orchestrator/remember/instructions.md +1 -1
  30. package/config/skills/orchestrator/reply-slack/execute.sh +45 -2
  31. package/config/skills/orchestrator/reply-slack/instructions.md +30 -2
  32. package/config/skills/orchestrator/report-bug/execute.sh +49 -0
  33. package/config/skills/orchestrator/report-bug/instructions.md +30 -0
  34. package/config/skills/orchestrator/report-bug/skill.json +20 -0
  35. package/config/slack-app-manifest.json +37 -0
  36. package/dist/backend/backend/src/constants.d.ts +38 -2
  37. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  38. package/dist/backend/backend/src/constants.js +44 -4
  39. package/dist/backend/backend/src/constants.js.map +1 -1
  40. package/dist/backend/backend/src/controllers/index.d.ts.map +1 -1
  41. package/dist/backend/backend/src/controllers/index.js +6 -0
  42. package/dist/backend/backend/src/controllers/index.js.map +1 -1
  43. package/dist/backend/backend/src/controllers/messaging/messenger.routes.d.ts +3 -0
  44. package/dist/backend/backend/src/controllers/messaging/messenger.routes.d.ts.map +1 -0
  45. package/dist/backend/backend/src/controllers/messaging/messenger.routes.js +80 -0
  46. package/dist/backend/backend/src/controllers/messaging/messenger.routes.js.map +1 -0
  47. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts +14 -0
  48. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts.map +1 -1
  49. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js +74 -0
  50. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js.map +1 -1
  51. package/dist/backend/backend/src/controllers/oauth/oauth.routes.d.ts +3 -0
  52. package/dist/backend/backend/src/controllers/oauth/oauth.routes.d.ts.map +1 -0
  53. package/dist/backend/backend/src/controllers/oauth/oauth.routes.js +127 -0
  54. package/dist/backend/backend/src/controllers/oauth/oauth.routes.js.map +1 -0
  55. package/dist/backend/backend/src/controllers/orchestrator/orchestrator.controller.d.ts.map +1 -1
  56. package/dist/backend/backend/src/controllers/orchestrator/orchestrator.controller.js +20 -34
  57. package/dist/backend/backend/src/controllers/orchestrator/orchestrator.controller.js.map +1 -1
  58. package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
  59. package/dist/backend/backend/src/controllers/slack/slack.controller.js +29 -0
  60. package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
  61. package/dist/backend/backend/src/controllers/system/scheduler.controller.d.ts.map +1 -1
  62. package/dist/backend/backend/src/controllers/system/scheduler.controller.js +73 -6
  63. package/dist/backend/backend/src/controllers/system/scheduler.controller.js.map +1 -1
  64. package/dist/backend/backend/src/controllers/task-management/assignments.controller.d.ts.map +1 -1
  65. package/dist/backend/backend/src/controllers/task-management/assignments.controller.js +9 -5
  66. package/dist/backend/backend/src/controllers/task-management/assignments.controller.js.map +1 -1
  67. package/dist/backend/backend/src/controllers/user/user.routes.d.ts +3 -0
  68. package/dist/backend/backend/src/controllers/user/user.routes.d.ts.map +1 -0
  69. package/dist/backend/backend/src/controllers/user/user.routes.js +45 -0
  70. package/dist/backend/backend/src/controllers/user/user.routes.js.map +1 -0
  71. package/dist/backend/backend/src/index.d.ts.map +1 -1
  72. package/dist/backend/backend/src/index.js +1 -1
  73. package/dist/backend/backend/src/index.js.map +1 -1
  74. package/dist/backend/backend/src/routes/modules/terminal.routes.d.ts.map +1 -1
  75. package/dist/backend/backend/src/routes/modules/terminal.routes.js +2 -0
  76. package/dist/backend/backend/src/routes/modules/terminal.routes.js.map +1 -1
  77. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +24 -2
  78. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  79. package/dist/backend/backend/src/services/agent/agent-registration.service.js +222 -101
  80. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  81. package/dist/backend/backend/src/services/agent/codex-runtime.service.d.ts +5 -1
  82. package/dist/backend/backend/src/services/agent/codex-runtime.service.d.ts.map +1 -1
  83. package/dist/backend/backend/src/services/agent/codex-runtime.service.js +11 -17
  84. package/dist/backend/backend/src/services/agent/codex-runtime.service.js.map +1 -1
  85. package/dist/backend/backend/src/services/agent/context-window-monitor.service.d.ts +21 -0
  86. package/dist/backend/backend/src/services/agent/context-window-monitor.service.d.ts.map +1 -1
  87. package/dist/backend/backend/src/services/agent/context-window-monitor.service.js +157 -11
  88. package/dist/backend/backend/src/services/agent/context-window-monitor.service.js.map +1 -1
  89. package/dist/backend/backend/src/services/agent/gemini-runtime.service.d.ts +33 -2
  90. package/dist/backend/backend/src/services/agent/gemini-runtime.service.d.ts.map +1 -1
  91. package/dist/backend/backend/src/services/agent/gemini-runtime.service.js +388 -20
  92. package/dist/backend/backend/src/services/agent/gemini-runtime.service.js.map +1 -1
  93. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts +1 -1
  94. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
  95. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +25 -2
  96. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
  97. package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.d.ts +17 -2
  98. package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -1
  99. package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js +133 -16
  100. package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -1
  101. package/dist/backend/backend/src/services/core/config.service.d.ts.map +1 -1
  102. package/dist/backend/backend/src/services/core/config.service.js +3 -2
  103. package/dist/backend/backend/src/services/core/config.service.js.map +1 -1
  104. package/dist/backend/backend/src/services/core/logger.service.d.ts +1 -0
  105. package/dist/backend/backend/src/services/core/logger.service.d.ts.map +1 -1
  106. package/dist/backend/backend/src/services/core/logger.service.js +22 -8
  107. package/dist/backend/backend/src/services/core/logger.service.js.map +1 -1
  108. package/dist/backend/backend/src/services/mcp-client.d.ts.map +1 -1
  109. package/dist/backend/backend/src/services/mcp-client.js +9 -4
  110. package/dist/backend/backend/src/services/mcp-client.js.map +1 -1
  111. package/dist/backend/backend/src/services/mcp-server.d.ts +4 -2
  112. package/dist/backend/backend/src/services/mcp-server.d.ts.map +1 -1
  113. package/dist/backend/backend/src/services/mcp-server.js +77 -18
  114. package/dist/backend/backend/src/services/mcp-server.js.map +1 -1
  115. package/dist/backend/backend/src/services/memory/memory.service.d.ts +1 -1
  116. package/dist/backend/backend/src/services/memory/memory.service.d.ts.map +1 -1
  117. package/dist/backend/backend/src/services/memory/memory.service.js +10 -1
  118. package/dist/backend/backend/src/services/memory/memory.service.js.map +1 -1
  119. package/dist/backend/backend/src/services/memory/project-memory.service.d.ts.map +1 -1
  120. package/dist/backend/backend/src/services/memory/project-memory.service.js +11 -2
  121. package/dist/backend/backend/src/services/memory/project-memory.service.js.map +1 -1
  122. package/dist/backend/backend/src/services/messaging/adapters/discord-messenger.adapter.d.ts +13 -0
  123. package/dist/backend/backend/src/services/messaging/adapters/discord-messenger.adapter.d.ts.map +1 -0
  124. package/dist/backend/backend/src/services/messaging/adapters/discord-messenger.adapter.js +45 -0
  125. package/dist/backend/backend/src/services/messaging/adapters/discord-messenger.adapter.js.map +1 -0
  126. package/dist/backend/backend/src/services/messaging/adapters/slack-messenger.adapter.d.ts +13 -0
  127. package/dist/backend/backend/src/services/messaging/adapters/slack-messenger.adapter.d.ts.map +1 -0
  128. package/dist/backend/backend/src/services/messaging/adapters/slack-messenger.adapter.js +27 -0
  129. package/dist/backend/backend/src/services/messaging/adapters/slack-messenger.adapter.js.map +1 -0
  130. package/dist/backend/backend/src/services/messaging/adapters/telegram-messenger.adapter.d.ts +13 -0
  131. package/dist/backend/backend/src/services/messaging/adapters/telegram-messenger.adapter.d.ts.map +1 -0
  132. package/dist/backend/backend/src/services/messaging/adapters/telegram-messenger.adapter.js +43 -0
  133. package/dist/backend/backend/src/services/messaging/adapters/telegram-messenger.adapter.js.map +1 -0
  134. package/dist/backend/backend/src/services/messaging/message-queue.service.d.ts.map +1 -1
  135. package/dist/backend/backend/src/services/messaging/message-queue.service.js +17 -0
  136. package/dist/backend/backend/src/services/messaging/message-queue.service.js.map +1 -1
  137. package/dist/backend/backend/src/services/messaging/messenger-adapter.interface.d.ts +25 -0
  138. package/dist/backend/backend/src/services/messaging/messenger-adapter.interface.d.ts.map +1 -0
  139. package/dist/backend/backend/src/services/messaging/messenger-adapter.interface.js +2 -0
  140. package/dist/backend/backend/src/services/messaging/messenger-adapter.interface.js.map +1 -0
  141. package/dist/backend/backend/src/services/messaging/messenger-registry.service.d.ts +14 -0
  142. package/dist/backend/backend/src/services/messaging/messenger-registry.service.d.ts.map +1 -0
  143. package/dist/backend/backend/src/services/messaging/messenger-registry.service.js +20 -0
  144. package/dist/backend/backend/src/services/messaging/messenger-registry.service.js.map +1 -0
  145. package/dist/backend/backend/src/services/orchestrator/orchestrator-heartbeat-monitor.service.d.ts +3 -1
  146. package/dist/backend/backend/src/services/orchestrator/orchestrator-heartbeat-monitor.service.d.ts.map +1 -1
  147. package/dist/backend/backend/src/services/orchestrator/orchestrator-heartbeat-monitor.service.js +15 -2
  148. package/dist/backend/backend/src/services/orchestrator/orchestrator-heartbeat-monitor.service.js.map +1 -1
  149. package/dist/backend/backend/src/services/orchestrator/orchestrator-restart.service.d.ts +5 -0
  150. package/dist/backend/backend/src/services/orchestrator/orchestrator-restart.service.d.ts.map +1 -1
  151. package/dist/backend/backend/src/services/orchestrator/orchestrator-restart.service.js +42 -2
  152. package/dist/backend/backend/src/services/orchestrator/orchestrator-restart.service.js.map +1 -1
  153. package/dist/backend/backend/src/services/session/pty/pty-session-backend.d.ts +39 -0
  154. package/dist/backend/backend/src/services/session/pty/pty-session-backend.d.ts.map +1 -1
  155. package/dist/backend/backend/src/services/session/pty/pty-session-backend.js +121 -1
  156. package/dist/backend/backend/src/services/session/pty/pty-session-backend.js.map +1 -1
  157. package/dist/backend/backend/src/services/session/pty/pty-terminal-buffer.d.ts +4 -0
  158. package/dist/backend/backend/src/services/session/pty/pty-terminal-buffer.d.ts.map +1 -1
  159. package/dist/backend/backend/src/services/session/pty/pty-terminal-buffer.js +7 -3
  160. package/dist/backend/backend/src/services/session/pty/pty-terminal-buffer.js.map +1 -1
  161. package/dist/backend/backend/src/services/settings/settings.service.d.ts.map +1 -1
  162. package/dist/backend/backend/src/services/settings/settings.service.js +6 -0
  163. package/dist/backend/backend/src/services/settings/settings.service.js.map +1 -1
  164. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +2 -2
  165. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  166. package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
  167. package/dist/backend/backend/src/services/slack/slack.service.js +14 -0
  168. package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
  169. package/dist/backend/backend/src/services/user/user-identity.service.d.ts +42 -0
  170. package/dist/backend/backend/src/services/user/user-identity.service.d.ts.map +1 -0
  171. package/dist/backend/backend/src/services/user/user-identity.service.js +123 -0
  172. package/dist/backend/backend/src/services/user/user-identity.service.js.map +1 -0
  173. package/dist/backend/backend/src/services/workflow/message-scheduler.service.d.ts.map +1 -1
  174. package/dist/backend/backend/src/services/workflow/message-scheduler.service.js +8 -1
  175. package/dist/backend/backend/src/services/workflow/message-scheduler.service.js.map +1 -1
  176. package/dist/backend/backend/src/services/workflow/scheduler.service.d.ts +21 -2
  177. package/dist/backend/backend/src/services/workflow/scheduler.service.d.ts.map +1 -1
  178. package/dist/backend/backend/src/services/workflow/scheduler.service.js +109 -6
  179. package/dist/backend/backend/src/services/workflow/scheduler.service.js.map +1 -1
  180. package/dist/backend/backend/src/types/chat.types.d.ts.map +1 -1
  181. package/dist/backend/backend/src/types/chat.types.js +2 -3
  182. package/dist/backend/backend/src/types/chat.types.js.map +1 -1
  183. package/dist/backend/backend/src/types/index.d.ts +8 -0
  184. package/dist/backend/backend/src/types/index.d.ts.map +1 -1
  185. package/dist/backend/backend/src/types/index.js.map +1 -1
  186. package/dist/backend/backend/src/types/memory.types.d.ts +1 -1
  187. package/dist/backend/backend/src/types/memory.types.d.ts.map +1 -1
  188. package/dist/backend/backend/src/types/memory.types.js.map +1 -1
  189. package/dist/backend/backend/src/types/settings.types.d.ts +11 -0
  190. package/dist/backend/backend/src/types/settings.types.d.ts.map +1 -1
  191. package/dist/backend/backend/src/types/settings.types.js +23 -1
  192. package/dist/backend/backend/src/types/settings.types.js.map +1 -1
  193. package/dist/backend/config/constants.d.ts +6 -0
  194. package/dist/backend/config/constants.d.ts.map +1 -1
  195. package/dist/backend/config/constants.js +6 -0
  196. package/dist/backend/config/constants.js.map +1 -1
  197. package/dist/backend/config/index.d.ts +3 -0
  198. package/dist/backend/config/index.d.ts.map +1 -1
  199. package/dist/cli/backend/src/constants.d.ts +38 -2
  200. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  201. package/dist/cli/backend/src/constants.js +44 -4
  202. package/dist/cli/backend/src/constants.js.map +1 -1
  203. package/dist/cli/backend/src/services/core/config.service.d.ts.map +1 -1
  204. package/dist/cli/backend/src/services/core/config.service.js +3 -2
  205. package/dist/cli/backend/src/services/core/config.service.js.map +1 -1
  206. package/dist/cli/backend/src/services/core/logger.service.d.ts +1 -0
  207. package/dist/cli/backend/src/services/core/logger.service.d.ts.map +1 -1
  208. package/dist/cli/backend/src/services/core/logger.service.js +22 -8
  209. package/dist/cli/backend/src/services/core/logger.service.js.map +1 -1
  210. package/dist/cli/backend/src/services/mcp-server.d.ts +4 -2
  211. package/dist/cli/backend/src/services/mcp-server.d.ts.map +1 -1
  212. package/dist/cli/backend/src/services/mcp-server.js +77 -18
  213. package/dist/cli/backend/src/services/mcp-server.js.map +1 -1
  214. package/dist/cli/backend/src/services/memory/memory.service.d.ts +1 -1
  215. package/dist/cli/backend/src/services/memory/memory.service.d.ts.map +1 -1
  216. package/dist/cli/backend/src/services/memory/memory.service.js +10 -1
  217. package/dist/cli/backend/src/services/memory/memory.service.js.map +1 -1
  218. package/dist/cli/backend/src/services/memory/project-memory.service.d.ts.map +1 -1
  219. package/dist/cli/backend/src/services/memory/project-memory.service.js +11 -2
  220. package/dist/cli/backend/src/services/memory/project-memory.service.js.map +1 -1
  221. package/dist/cli/backend/src/types/chat.types.d.ts.map +1 -1
  222. package/dist/cli/backend/src/types/chat.types.js +2 -3
  223. package/dist/cli/backend/src/types/chat.types.js.map +1 -1
  224. package/dist/cli/backend/src/types/index.d.ts +8 -0
  225. package/dist/cli/backend/src/types/index.d.ts.map +1 -1
  226. package/dist/cli/backend/src/types/index.js.map +1 -1
  227. package/dist/cli/backend/src/types/memory.types.d.ts +1 -1
  228. package/dist/cli/backend/src/types/memory.types.d.ts.map +1 -1
  229. package/dist/cli/backend/src/types/memory.types.js.map +1 -1
  230. package/dist/cli/backend/src/types/settings.types.d.ts +11 -0
  231. package/dist/cli/backend/src/types/settings.types.d.ts.map +1 -1
  232. package/dist/cli/backend/src/types/settings.types.js +23 -1
  233. package/dist/cli/backend/src/types/settings.types.js.map +1 -1
  234. package/dist/cli/cli/src/index.js +1 -0
  235. package/dist/cli/cli/src/index.js.map +1 -1
  236. package/dist/cli/config/constants.d.ts +6 -0
  237. package/dist/cli/config/constants.d.ts.map +1 -1
  238. package/dist/cli/config/constants.js +6 -0
  239. package/dist/cli/config/constants.js.map +1 -1
  240. package/dist/cli/config/index.d.ts +3 -0
  241. package/dist/cli/config/index.d.ts.map +1 -1
  242. package/frontend/dist/assets/{index-0a245b0d.js → index-45eeea99.js} +2 -2
  243. package/frontend/dist/index.html +1 -1
  244. package/package.json +1 -1
@@ -4,7 +4,7 @@ import { readFile, readdir, stat, mkdir, writeFile } from 'fs/promises';
4
4
  import { LoggerService } from '../core/logger.service.js';
5
5
  import { createSessionCommandHelper, getSessionBackendSync, createSessionBackend, getSessionStatePersistence, } from '../session/index.js';
6
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';
7
+ import { CREWLY_CONSTANTS, ENV_CONSTANTS, AGENT_TIMEOUTS, ORCHESTRATOR_SESSION_NAME, ORCHESTRATOR_ROLE, RUNTIME_TYPES, SESSION_COMMAND_DELAYS, EVENT_DELIVERY_CONSTANTS, TERMINAL_PATTERNS, GEMINI_SHELL_MODE_CONSTANTS, } from '../../constants.js';
8
8
  import { WEB_CONSTANTS } from '../../../../config/constants.js';
9
9
  import { delay } from '../../utils/async.utils.js';
10
10
  import { getSettingsService } from '../settings/settings.service.js';
@@ -41,6 +41,10 @@ export class AgentRegistrationService {
41
41
  // Track recently-sent messages for background stuck-detection (all runtimes).
42
42
  // Key: sessionName, Value: array of tracked message entries
43
43
  sentMessageTracker = new Map();
44
+ // Per-session delivery mutex to serialize message delivery.
45
+ // Prevents concurrent sendMessageWithRetry calls to the same session,
46
+ // which causes multiple Ctrl+C presses that can crash the runtime.
47
+ sessionDeliveryMutex = new Map();
44
48
  // Terminal patterns are now centralized in TERMINAL_PATTERNS constant
45
49
  // Keeping these as static getters for backwards compatibility within the class
46
50
  static get CLAUDE_PROMPT_INDICATORS() {
@@ -119,6 +123,39 @@ export class AgentRegistrationService {
119
123
  }
120
124
  return this._sessionHelper;
121
125
  }
126
+ /**
127
+ * Resolve the runtime type for a session from storage.
128
+ * Checks orchestrator status first, then team member data.
129
+ * Falls back to CLAUDE_CODE if nothing is found.
130
+ *
131
+ * IMPORTANT: Callers should prefer passing runtimeType explicitly.
132
+ * This method exists as a safety net so that Ctrl+C (Claude Code
133
+ * cleanup) is never sent to a Gemini CLI session, which would
134
+ * trigger /quit and kill the agent.
135
+ */
136
+ async resolveSessionRuntimeType(sessionName) {
137
+ try {
138
+ // Check if this is the orchestrator session
139
+ if (sessionName === ORCHESTRATOR_SESSION_NAME) {
140
+ const orchestratorStatus = await this.storageService.getOrchestratorStatus();
141
+ if (orchestratorStatus?.runtimeType) {
142
+ return orchestratorStatus.runtimeType;
143
+ }
144
+ }
145
+ // Check team member data
146
+ const memberInfo = await this.storageService.findMemberBySessionName(sessionName);
147
+ if (memberInfo?.member?.runtimeType) {
148
+ return memberInfo.member.runtimeType;
149
+ }
150
+ }
151
+ catch (error) {
152
+ this.logger.debug('Could not resolve runtime type from storage, using default', {
153
+ sessionName,
154
+ error: error instanceof Error ? error.message : String(error),
155
+ });
156
+ }
157
+ return RUNTIME_TYPES.CLAUDE_CODE;
158
+ }
122
159
  /**
123
160
  * Find the project root by looking for package.json
124
161
  */
@@ -224,7 +261,7 @@ export class AgentRegistrationService {
224
261
  * Initialize agent with optimized 2-step escalation process
225
262
  * Reduced from 4-step to 2-step with shorter timeouts for better concurrency
226
263
  */
227
- async initializeAgentWithRegistration(sessionName, role, projectPath, timeout = AGENT_TIMEOUTS.REGULAR_AGENT_INITIALIZATION, memberId, runtimeType = RUNTIME_TYPES.CLAUDE_CODE, runtimeFlags) {
264
+ async initializeAgentWithRegistration(sessionName, role, projectPath, timeout = AGENT_TIMEOUTS.REGULAR_AGENT_INITIALIZATION, memberId, runtimeType = RUNTIME_TYPES.CLAUDE_CODE, runtimeFlags, additionalAllowlistPaths) {
228
265
  const startTime = Date.now();
229
266
  this.logger.info('Starting optimized agent initialization with registration', {
230
267
  sessionName,
@@ -245,7 +282,7 @@ export class AgentRegistrationService {
245
282
  sessionName,
246
283
  });
247
284
  const step1Success = await this.tryCleanupAndReinit(sessionName, role, 70000, // 70 seconds for cleanup and reinit (allows 60s for runtime ready)
248
- projectPath, memberId, runtimeType, runtimeFlags);
285
+ projectPath, memberId, runtimeType, runtimeFlags, additionalAllowlistPaths);
249
286
  if (step1Success) {
250
287
  return {
251
288
  success: true,
@@ -325,7 +362,7 @@ export class AgentRegistrationService {
325
362
  * Step 2: Cleanup with Ctrl+C and reinitialize
326
363
  * Tries to reset the runtime session and start fresh
327
364
  */
328
- async tryCleanupAndReinit(sessionName, role, timeout, projectPath, memberId, runtimeType = RUNTIME_TYPES.CLAUDE_CODE, runtimeFlags) {
365
+ async tryCleanupAndReinit(sessionName, role, timeout, projectPath, memberId, runtimeType = RUNTIME_TYPES.CLAUDE_CODE, runtimeFlags, additionalAllowlistPaths) {
329
366
  // Clear Commandline
330
367
  await (await this.getSessionHelper()).clearCurrentCommandLine(sessionName);
331
368
  // Inject --resume flag if this was a previously running Claude Code session
@@ -360,7 +397,7 @@ export class AgentRegistrationService {
360
397
  if (runtimeType === RUNTIME_TYPES.CLAUDE_CODE) {
361
398
  try {
362
399
  const prompt = await this.loadRegistrationPrompt(role, sessionName, memberId);
363
- promptFilePath = await this.writePromptFile(sessionName, prompt, runtimeType);
400
+ promptFilePath = await this.writePromptFile(sessionName, prompt);
364
401
  }
365
402
  catch (promptError) {
366
403
  this.logger.warn('Failed to pre-write prompt file (non-fatal, will fall back to direct delivery)', {
@@ -387,7 +424,7 @@ export class AgentRegistrationService {
387
424
  RuntimeExitMonitorService.getInstance().startMonitoring(sessionName, runtimeType, role);
388
425
  // Run post-initialization hook (e.g., Gemini CLI directory allowlist)
389
426
  try {
390
- await runtimeService2.postInitialize(sessionName, projectPath);
427
+ await runtimeService2.postInitialize(sessionName, projectPath, additionalAllowlistPaths);
391
428
  // Drain stale terminal escape sequences (e.g. DA1 [?1;2c) that may have
392
429
  // arrived during postInitialize commands, so they don't leak into the prompt input
393
430
  await delay(500);
@@ -502,13 +539,22 @@ export class AgentRegistrationService {
502
539
  const controller = new AbortController();
503
540
  this.registrationAbortControllers.set(sessionName, controller);
504
541
  try {
542
+ this.logger.info('Loading registration prompt', { sessionName, role, runtimeType });
505
543
  if (controller.signal.aborted)
506
544
  return;
507
545
  const prompt = await this.loadRegistrationPrompt(role, sessionName, memberId);
546
+ this.logger.info('Registration prompt loaded, sending to agent', {
547
+ sessionName, role, runtimeType, promptLength: prompt.length,
548
+ });
508
549
  if (controller.signal.aborted)
509
550
  return;
510
- await this.sendPromptRobustly(sessionName, prompt, runtimeType, controller.signal);
511
- this.logger.debug('Registration prompt sent asynchronously', { sessionName, role });
551
+ const sent = await this.sendPromptRobustly(sessionName, prompt, runtimeType, controller.signal);
552
+ if (sent) {
553
+ this.logger.info('Registration prompt sent successfully', { sessionName, role });
554
+ }
555
+ else {
556
+ this.logger.warn('Registration prompt delivery returned false', { sessionName, role, runtimeType });
557
+ }
512
558
  }
513
559
  catch (error) {
514
560
  if (controller.signal.aborted) {
@@ -518,6 +564,7 @@ export class AgentRegistrationService {
518
564
  this.logger.warn('Failed to send registration prompt asynchronously', {
519
565
  sessionName,
520
566
  error: error instanceof Error ? error.message : String(error),
567
+ stack: error instanceof Error ? error.stack : undefined,
521
568
  });
522
569
  }
523
570
  finally {
@@ -565,7 +612,7 @@ export class AgentRegistrationService {
565
612
  if (runtimeType === RUNTIME_TYPES.CLAUDE_CODE) {
566
613
  try {
567
614
  const prompt = await this.loadRegistrationPrompt(role, sessionName, memberId);
568
- promptFilePath = await this.writePromptFile(sessionName, prompt, runtimeType);
615
+ promptFilePath = await this.writePromptFile(sessionName, prompt);
569
616
  }
570
617
  catch (promptError) {
571
618
  this.logger.warn('Failed to pre-write prompt file in full recreation (non-fatal)', {
@@ -598,16 +645,27 @@ export class AgentRegistrationService {
598
645
  // Wait a bit longer for runtime to fully load after showing welcome message
599
646
  this.logger.debug('Runtime ready detected for orchestrator, waiting for full startup before verification', { sessionName, runtimeType });
600
647
  await delay(5000);
601
- this.logger.debug('Verifying orchestrator runtime responsiveness', {
602
- sessionName,
603
- runtimeType,
604
- });
605
- // runtimeService4: Final verification instance for orchestrator responsiveness
606
- // Clean instance for post-initialization responsiveness testing
607
- const runtimeService4 = this.createRuntimeService(runtimeType);
608
- const runtimeResponding = await runtimeService4.detectRuntimeWithCommand(sessionName);
609
- if (!runtimeResponding) {
610
- throw new Error(`${runtimeType} not responding to commands after orchestrator recreation`);
648
+ // Codex CLI is sensitive to active key-probe verification (it may drop to
649
+ // the shell when probe keys are interpreted outside the TUI). We already
650
+ // passed waitForRuntimeReady above, so skip the extra command probe here.
651
+ if (runtimeType !== RUNTIME_TYPES.CODEX_CLI) {
652
+ this.logger.debug('Verifying orchestrator runtime responsiveness', {
653
+ sessionName,
654
+ runtimeType,
655
+ });
656
+ // runtimeService4: Final verification instance for orchestrator responsiveness
657
+ // Clean instance for post-initialization responsiveness testing
658
+ const runtimeService4 = this.createRuntimeService(runtimeType);
659
+ const runtimeResponding = await runtimeService4.detectRuntimeWithCommand(sessionName);
660
+ if (!runtimeResponding) {
661
+ throw new Error(`${runtimeType} not responding to commands after orchestrator recreation`);
662
+ }
663
+ }
664
+ else {
665
+ this.logger.debug('Skipping active runtime probe for Codex orchestrator recreation', {
666
+ sessionName,
667
+ runtimeType,
668
+ });
611
669
  }
612
670
  this.logger.debug('Runtime confirmed ready for orchestrator in Step 3', { sessionName, runtimeType });
613
671
  }
@@ -789,6 +847,24 @@ export class AgentRegistrationService {
789
847
  if (memberId) {
790
848
  prompt += `\n- **Member ID:** ${memberId}`;
791
849
  }
850
+ // Conditionally append Self Evolution instructions for the orchestrator
851
+ if (role === ORCHESTRATOR_ROLE) {
852
+ try {
853
+ const settings = await getSettingsService().getSettings();
854
+ if (settings.general.enableSelfEvolution) {
855
+ const selfEvoPath = path.join(this.projectRoot, 'config', 'roles', 'orchestrator', 'self-evolution.md');
856
+ const selfEvoPrompt = await readFile(selfEvoPath, 'utf8');
857
+ prompt += `\n\n---\n\n${selfEvoPrompt}`;
858
+ this.logger.info('Self Evolution prompt injected', { sessionName });
859
+ }
860
+ }
861
+ catch (selfEvoError) {
862
+ this.logger.warn('Failed to load self-evolution prompt (non-critical)', {
863
+ sessionName,
864
+ error: selfEvoError instanceof Error ? selfEvoError.message : String(selfEvoError),
865
+ });
866
+ }
867
+ }
792
868
  return prompt;
793
869
  }
794
870
  catch (error) {
@@ -825,15 +901,11 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
825
901
  *
826
902
  * @param sessionName - Session name (used for filename)
827
903
  * @param prompt - The full prompt content
828
- * @param runtimeType - Runtime type (determines directory)
829
904
  * @returns The absolute path to the written file, or undefined on error
830
905
  */
831
- async writePromptFile(sessionName, prompt, runtimeType) {
832
- const isClaudeCode = runtimeType === RUNTIME_TYPES.CLAUDE_CODE;
833
- const promptsDir = isClaudeCode
834
- ? path.join(os.homedir(), CREWLY_CONSTANTS.PATHS.CREWLY_HOME, 'prompts')
835
- : path.join(this.projectRoot, '.crewly', 'prompts');
836
- const promptFilePath = path.join(promptsDir, `${sessionName}-init.md`);
906
+ async writePromptFile(sessionName, prompt) {
907
+ const promptFilePath = this.getInitPromptFilePath(sessionName);
908
+ const promptsDir = path.dirname(promptFilePath);
837
909
  try {
838
910
  await mkdir(promptsDir, { recursive: true });
839
911
  await writeFile(promptFilePath, prompt, 'utf8');
@@ -853,6 +925,12 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
853
925
  return undefined;
854
926
  }
855
927
  }
928
+ /**
929
+ * Build the unified init prompt path used by all runtimes.
930
+ */
931
+ getInitPromptFilePath(sessionName) {
932
+ return path.join(os.homedir(), CREWLY_CONSTANTS.PATHS.CREWLY_HOME, 'prompts', `${sessionName}-init.md`);
933
+ }
856
934
  /**
857
935
  * Wait for agent registration to complete
858
936
  */
@@ -1245,7 +1323,13 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
1245
1323
  }
1246
1324
  }
1247
1325
  // Step 2: If runtime not detected or registration failed, try Ctrl+C restart
1326
+ // only for Claude. Gemini/Codex treat Ctrl+C as destructive at prompt
1327
+ // and can exit the runtime instead of recovering.
1248
1328
  if (!recoverySuccess) {
1329
+ if (runtimeType !== RUNTIME_TYPES.CLAUDE_CODE) {
1330
+ this.logger.info('Skipping Ctrl+C recovery for non-Claude runtime; falling back to recreation', { sessionName, runtimeType });
1331
+ throw new Error(`${runtimeType} recovery requires full recreation`);
1332
+ }
1249
1333
  this.logger.info('Runtime not detected or registration failed, attempting Ctrl+C restart', {
1250
1334
  sessionName,
1251
1335
  });
@@ -1291,7 +1375,7 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
1291
1375
  }
1292
1376
  // Start context window monitoring for recovered non-orchestrator session
1293
1377
  if (role !== ORCHESTRATOR_ROLE && config.teamId && config.memberId) {
1294
- ContextWindowMonitorService.getInstance().startSessionMonitoring(sessionName, config.memberId, config.teamId, role);
1378
+ ContextWindowMonitorService.getInstance().startSessionMonitoring(sessionName, config.memberId, config.teamId, role, runtimeType);
1295
1379
  }
1296
1380
  return {
1297
1381
  success: true,
@@ -1366,7 +1450,7 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
1366
1450
  const timeout = role === ORCHESTRATOR_ROLE
1367
1451
  ? AGENT_TIMEOUTS.ORCHESTRATOR_INITIALIZATION
1368
1452
  : AGENT_TIMEOUTS.REGULAR_AGENT_INITIALIZATION;
1369
- const initResult = await this.initializeAgentWithRegistration(sessionName, role, projectPath, timeout, memberId, runtimeType, runtimeFlags);
1453
+ const initResult = await this.initializeAgentWithRegistration(sessionName, role, projectPath, timeout, memberId, runtimeType, runtimeFlags, config.additionalAllowlistPaths);
1370
1454
  if (!initResult.success) {
1371
1455
  return {
1372
1456
  success: false,
@@ -1376,7 +1460,7 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
1376
1460
  }
1377
1461
  // Start context window monitoring for newly created non-orchestrator session
1378
1462
  if (role !== ORCHESTRATOR_ROLE && config.teamId && config.memberId) {
1379
- ContextWindowMonitorService.getInstance().startSessionMonitoring(sessionName, config.memberId, config.teamId, role);
1463
+ ContextWindowMonitorService.getInstance().startSessionMonitoring(sessionName, config.memberId, config.teamId, role, runtimeType);
1380
1464
  }
1381
1465
  return {
1382
1466
  success: true,
@@ -1490,7 +1574,22 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
1490
1574
  * }
1491
1575
  * ```
1492
1576
  */
1493
- async sendMessageToAgent(sessionName, message, runtimeType = RUNTIME_TYPES.CLAUDE_CODE) {
1577
+ async sendMessageToAgent(sessionName, message, runtimeType) {
1578
+ // Per-session delivery serialization: chain this delivery after any
1579
+ // in-flight delivery to the same session. This prevents concurrent
1580
+ // sendMessageWithRetry calls that each send Ctrl+C on retry attempts,
1581
+ // which can kill the runtime when 25+ scheduled checks fire at once.
1582
+ const previousDelivery = this.sessionDeliveryMutex.get(sessionName);
1583
+ let releaseMutex;
1584
+ const currentDelivery = new Promise((r) => { releaseMutex = r; });
1585
+ this.sessionDeliveryMutex.set(sessionName, currentDelivery);
1586
+ if (previousDelivery) {
1587
+ this.logger.info('Waiting for in-flight delivery to complete before sending', {
1588
+ sessionName,
1589
+ messageLength: message.length,
1590
+ });
1591
+ await previousDelivery.catch(() => { });
1592
+ }
1494
1593
  try {
1495
1594
  if (!message || typeof message !== 'string') {
1496
1595
  return {
@@ -1498,6 +1597,12 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
1498
1597
  error: 'Message is required and must be a string',
1499
1598
  };
1500
1599
  }
1600
+ // Auto-resolve runtime type if not provided.
1601
+ // CRITICAL: defaulting to CLAUDE_CODE is dangerous because
1602
+ // Ctrl+C cleanup (Claude Code behavior) triggers /quit on Gemini CLI.
1603
+ if (!runtimeType) {
1604
+ runtimeType = await this.resolveSessionRuntimeType(sessionName);
1605
+ }
1501
1606
  // Get session helper once for this method
1502
1607
  const sessionHelper = await this.getSessionHelper();
1503
1608
  // Check if session exists
@@ -1510,8 +1615,11 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
1510
1615
  // Guard: refuse to deliver if the runtime process has exited.
1511
1616
  // When Claude Code exits (e.g. context exhaustion), the PTY shell
1512
1617
  // stays alive. Writing to it would execute garbage as shell commands.
1513
- const backend = sessionHelper.getBackend();
1514
- if (backend.isChildProcessAlive && !backend.isChildProcessAlive(sessionName)) {
1618
+ const backend = typeof sessionHelper.getBackend === 'function'
1619
+ ? sessionHelper.getBackend()
1620
+ : getSessionBackendSync();
1621
+ const childAlive = backend?.isChildProcessAlive?.(sessionName);
1622
+ if (childAlive === false) {
1515
1623
  this.logger.error('Runtime process not alive, refusing to deliver message', {
1516
1624
  sessionName,
1517
1625
  messageLength: message.length,
@@ -1549,6 +1657,12 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
1549
1657
  error: errorMessage,
1550
1658
  };
1551
1659
  }
1660
+ finally {
1661
+ releaseMutex();
1662
+ if (this.sessionDeliveryMutex.get(sessionName) === currentDelivery) {
1663
+ this.sessionDeliveryMutex.delete(sessionName);
1664
+ }
1665
+ }
1552
1666
  }
1553
1667
  /**
1554
1668
  * Wait for an agent session to be at a ready prompt before sending messages.
@@ -2019,11 +2133,9 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
2019
2133
  await sessionHelper.sendEnter(sessionName);
2020
2134
  await delay(500);
2021
2135
  }
2022
- // For Gemini CLI: detect and clear interactive modes before sending message.
2023
- // The Gemini CLI can enter interactive states (file picker from /resume,
2024
- // command palette, error prompts) where pasted text is consumed by the
2025
- // interactive UI rather than the main input. Send Ctrl-C to clear these
2026
- // states before writing the actual message.
2136
+ // For Gemini CLI: detect and gently escape interactive modes before
2137
+ // sending the message. Avoid Ctrl-C here Gemini interprets it as
2138
+ // destructive cancellation/quit in some states.
2027
2139
  if (!isClaudeCode) {
2028
2140
  const preOutput = sessionHelper.capturePane(sessionName, 10);
2029
2141
  const interactiveModePatterns = [
@@ -2034,12 +2146,14 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
2034
2146
  ];
2035
2147
  const inInteractiveMode = interactiveModePatterns.some(p => preOutput.includes(p));
2036
2148
  if (inInteractiveMode) {
2037
- this.logger.warn('Gemini CLI in interactive mode, sending Ctrl-C to clear', {
2149
+ this.logger.warn('Gemini CLI in interactive mode, using non-destructive recovery', {
2038
2150
  sessionName,
2039
2151
  detectedPattern: interactiveModePatterns.find(p => preOutput.includes(p)),
2040
2152
  });
2041
- await sessionHelper.sendKey(sessionName, 'C-c');
2042
- await new Promise(r => setTimeout(r, 1500));
2153
+ await sessionHelper.sendKey(sessionName, 'Tab');
2154
+ await delay(250);
2155
+ await sessionHelper.sendEnter(sessionName);
2156
+ await delay(1000);
2043
2157
  }
2044
2158
  }
2045
2159
  // Capture output BEFORE sending to detect changes for ALL runtimes.
@@ -2275,8 +2389,14 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
2275
2389
  const hasGeminiIndicators = newContent.length > 0
2276
2390
  && /reading|thinking|processing|analyzing|generating|searching/i.test(newContent);
2277
2391
  const significantLengthChange = Math.abs(lengthDiff) > 10;
2392
+ // For Gemini CLI, contentChanged alone is sufficient evidence of
2393
+ // delivery. The TUI redraws minimally (lengthDiff can be as low as
2394
+ // 1-2 chars) when processing starts, so requiring significantLengthChange
2395
+ // causes false "stuck" detection. False "stuck" + Ctrl+C kills the CLI.
2396
+ const isGemini = runtimeType === RUNTIME_TYPES.GEMINI_CLI;
2278
2397
  const delivered = (lengthDiff > 20)
2279
2398
  || (contentChanged && significantLengthChange)
2399
+ || (contentChanged && isGemini)
2280
2400
  || hasProcessingIndicators
2281
2401
  || hasGeminiIndicators;
2282
2402
  if (delivered) {
@@ -2311,31 +2431,18 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
2311
2431
  await sessionHelper.clearCurrentCommandLine(sessionName);
2312
2432
  }
2313
2433
  else {
2314
- // Gemini CLI retry cleanup: be careful with cleanup keystrokes.
2315
- // If output changed (contentChanged=true), text likely reached the
2316
- // input box but wasn't submitted Ctrl+C safely clears it.
2317
- // If output did NOT change at all, the TUI input is likely defocused
2318
- // and the input box is EMPTY. Ctrl+C on an empty Gemini CLI prompt
2319
- // triggers /quit and exits the CLI entirely.
2320
- const noOutputChange = !isClaudeCode && beforeOutput === sessionHelper.capturePane(sessionName, 20);
2321
- if (noOutputChange) {
2322
- this.logger.warn('No output change detected — TUI input likely defocused, using Tab to cycle Ink focus', {
2323
- sessionName,
2324
- attempt,
2325
- });
2326
- // Tab triggers focusNext() in Ink's FocusContext, which should
2327
- // cycle focus back to the InputPrompt component even when it's
2328
- // defocused. This is more reliable than Enter (which is silently
2329
- // consumed when no component is focused).
2330
- await sessionHelper.sendKey(sessionName, 'Tab');
2331
- await delay(300);
2332
- await sessionHelper.sendEnter(sessionName);
2333
- await delay(300);
2334
- }
2335
- else {
2336
- await sessionHelper.sendCtrlC(sessionName);
2337
- await delay(200);
2338
- }
2434
+ // Gemini CLI retry cleanup: NEVER send Ctrl+C it triggers /quit
2435
+ // and kills the CLI entirely, regardless of whether text is in the
2436
+ // input box or not. Use Tab to cycle Ink focus back to the input
2437
+ // prompt, then Enter to submit any pending text.
2438
+ this.logger.warn('Gemini CLI message stuck using Tab focus recovery (no Ctrl+C)', {
2439
+ sessionName,
2440
+ attempt,
2441
+ });
2442
+ await sessionHelper.sendKey(sessionName, 'Tab');
2443
+ await delay(300);
2444
+ await sessionHelper.sendEnter(sessionName);
2445
+ await delay(300);
2339
2446
  }
2340
2447
  await delay(SESSION_COMMAND_DELAYS.CLEAR_COMMAND_DELAY);
2341
2448
  if (attempt < maxAttempts) {
@@ -2710,6 +2817,8 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
2710
2817
  return TERMINAL_PATTERNS.CLAUDE_CODE_PROMPT;
2711
2818
  if (runtimeType === RUNTIME_TYPES.GEMINI_CLI)
2712
2819
  return TERMINAL_PATTERNS.GEMINI_CLI_PROMPT;
2820
+ if (runtimeType === RUNTIME_TYPES.CODEX_CLI)
2821
+ return TERMINAL_PATTERNS.CODEX_CLI_PROMPT;
2713
2822
  return TERMINAL_PATTERNS.PROMPT_STREAM;
2714
2823
  }
2715
2824
  /**
@@ -2730,6 +2839,7 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
2730
2839
  const tailSection = terminalOutput.slice(-2000);
2731
2840
  const isGemini = runtimeType === RUNTIME_TYPES.GEMINI_CLI;
2732
2841
  const isClaudeCode = runtimeType === RUNTIME_TYPES.CLAUDE_CODE;
2842
+ const isCodex = runtimeType === RUNTIME_TYPES.CODEX_CLI;
2733
2843
  const streamPattern = this.getPromptPatternForRuntime(runtimeType);
2734
2844
  // Check for prompt FIRST. Processing indicators like "thinking" or "analyzing"
2735
2845
  // can appear in the agent's previous response text and persist in the terminal
@@ -2747,7 +2857,7 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
2747
2857
  // Strip TUI box-drawing borders (│, ┃, etc.) that Gemini CLI wraps around prompts
2748
2858
  const stripped = trimmed.replace(/^[│┃|]+\s*/, '').replace(/\s*[│┃|]+$/, '');
2749
2859
  // Claude Code prompts: ❯, ⏵, $ alone on a line
2750
- if (!isGemini) {
2860
+ if (!isGemini && !isCodex) {
2751
2861
  if (['❯', '⏵', '$'].some(ch => trimmed === ch || stripped === ch)) {
2752
2862
  return true;
2753
2863
  }
@@ -2760,7 +2870,14 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
2760
2870
  }
2761
2871
  // Gemini CLI prompts: > or ! followed by space
2762
2872
  if (!isClaudeCode) {
2763
- if (trimmed.startsWith('> ') || trimmed.startsWith('! ') ||
2873
+ if (isCodex) {
2874
+ // Codex prompt uses `›`; avoid plain `> ` to prevent false-positives
2875
+ // from markdown blockquotes in agent output.
2876
+ if (trimmed.startsWith('› ') || stripped.startsWith('› ')) {
2877
+ return true;
2878
+ }
2879
+ }
2880
+ else if (trimmed.startsWith('> ') || trimmed.startsWith('! ') ||
2764
2881
  stripped.startsWith('> ') || stripped.startsWith('! ')) {
2765
2882
  return true;
2766
2883
  }
@@ -2950,7 +3067,7 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
2950
3067
  const isClaudeCode = runtimeType === RUNTIME_TYPES.CLAUDE_CODE;
2951
3068
  const sessionHelper = await this.getSessionHelper();
2952
3069
  // Step 1: Write prompt to a file (idempotent — may already exist from pre-launch write).
2953
- const promptFilePath = await this.writePromptFile(sessionName, prompt, runtimeType);
3070
+ const promptFilePath = await this.writePromptFile(sessionName, prompt);
2954
3071
  if (!promptFilePath) {
2955
3072
  // File write failure is fatal — all runtimes use the file-read approach.
2956
3073
  return false;
@@ -2964,12 +3081,16 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
2964
3081
  const messageToSend = isClaudeCode
2965
3082
  ? 'Begin your work now. Follow the instructions you were given and start by doing an initial assessment of the project.'
2966
3083
  : `Read the file at ${promptFilePath} and follow all instructions in it.`;
2967
- // Claude Code: single attempt only. The full prompt is loaded via
2968
- // --append-system-prompt-file, so the kickoff is just a short trigger.
2969
- // Retrying causes duplicates because Claude returns to its prompt between
2970
- // tool calls, making retry checks think the first message wasn't received.
2971
- // Gemini CLI / other runtimes: up to 3 attempts (prompt loaded via file-read).
2972
- const maxAttempts = isClaudeCode ? 1 : 3;
3084
+ // Single attempt for all runtimes. Retrying causes duplicate messages because:
3085
+ // - Claude Code: returns to its prompt between tool calls, making retry
3086
+ // checks think the first message wasn't received.
3087
+ // - Gemini CLI: the echoed message "Read the file at..." starts with `> `
3088
+ // which isClaudeAtPrompt misdetects as an idle prompt, and the TUI's
3089
+ // input box shows `> ` even during processing. Both cause false-negative
3090
+ // delivery verification and unnecessary retries.
3091
+ // The sendMessage helper reliably writes to the PTY — if it succeeds,
3092
+ // the message was delivered.
3093
+ const maxAttempts = 1;
2973
3094
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2974
3095
  // Check abort before each attempt
2975
3096
  if (abortSignal?.aborted) {
@@ -2977,7 +3098,7 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
2977
3098
  return false;
2978
3099
  }
2979
3100
  try {
2980
- this.logger.debug('Sending prompt to agent', {
3101
+ this.logger.info('Sending prompt to agent', {
2981
3102
  sessionName,
2982
3103
  attempt,
2983
3104
  runtimeType,
@@ -2990,13 +3111,23 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
2990
3111
  return false;
2991
3112
  }
2992
3113
  // Clear any pending input before sending the instruction (first attempt only).
2993
- // Claude Code: Escape closes slash menus + Ctrl+U clears line.
2994
- // Gemini CLI (Ink TUI): Do NOT send any cleanup keystrokes.
2995
- if (isClaudeCode && attempt === 1) {
2996
- await sessionHelper.sendEscape(sessionName);
2997
- await delay(200);
2998
- await sessionHelper.sendKey(sessionName, 'C-u');
2999
- await delay(300);
3114
+ if (attempt === 1) {
3115
+ if (isClaudeCode) {
3116
+ // Claude Code: Escape closes slash menus + Ctrl+U clears line.
3117
+ await sessionHelper.sendEscape(sessionName);
3118
+ await delay(200);
3119
+ await sessionHelper.sendKey(sessionName, 'C-u');
3120
+ await delay(300);
3121
+ }
3122
+ else {
3123
+ // Gemini CLI (Ink TUI): After /directory add processing, the TUI
3124
+ // input may lose focus or have stale invisible characters.
3125
+ // Send Enter to flush any residual input, then wait for the
3126
+ // prompt to settle before sending the real instruction.
3127
+ this.logger.debug('Gemini CLI pre-send: flushing input with Enter', { sessionName });
3128
+ await sessionHelper.sendEnter(sessionName);
3129
+ await delay(1000);
3130
+ }
3000
3131
  }
3001
3132
  // Check abort right before writing instruction to terminal
3002
3133
  if (abortSignal?.aborted) {
@@ -3039,27 +3170,17 @@ After checking in, just say "Ready for tasks" and wait for me to send you work.`
3039
3170
  });
3040
3171
  return false;
3041
3172
  }
3042
- // Gemini CLI / other runtimes: original verification with length comparison
3043
- // Capture state before sending was skipped for Claude Code path above,
3044
- // so capture it here for non-Claude runtimes on retry.
3045
- const verifyDelay = 3000;
3046
- await delay(verifyDelay);
3047
- if (abortSignal?.aborted)
3048
- return false;
3049
- const afterOutput = sessionHelper.capturePane(sessionName, 20);
3050
- const hasProcessingIndicators = /thinking|processing|analyzing|registering|reading/i.test(afterOutput);
3051
- if (hasProcessingIndicators) {
3052
- this.logger.debug('Prompt instruction delivered successfully', {
3053
- sessionName, attempt, runtimeType,
3054
- });
3055
- return true;
3056
- }
3057
- this.logger.debug('Prompt instruction delivery unconfirmed - retrying', {
3173
+ // Gemini CLI / other runtimes: trust the PTY delivery.
3174
+ // sendMessage writes directly to the PTY if it didn't throw,
3175
+ // the message was delivered. Terminal-based verification is unreliable
3176
+ // because Gemini's TUI always shows `> ` (even during processing)
3177
+ // and our echoed message starts with `> `, both causing false
3178
+ // "at prompt" detection.
3179
+ this.logger.info('Prompt instruction sent to PTY (trusting delivery)', {
3058
3180
  sessionName, attempt, runtimeType,
3181
+ messageLength: messageToSend.length,
3059
3182
  });
3060
- if (attempt < maxAttempts) {
3061
- await delay(1000);
3062
- }
3183
+ return true;
3063
3184
  }
3064
3185
  catch (error) {
3065
3186
  this.logger.error('Error during prompt instruction delivery', {