crewly 1.20.134 → 1.20.136

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 (74) hide show
  1. package/config/skills/agent/core/block-task/SKILL.md +16 -3
  2. package/dist/backend/backend/src/constants.d.ts +4 -0
  3. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  4. package/dist/backend/backend/src/constants.js +4 -0
  5. package/dist/backend/backend/src/constants.js.map +1 -1
  6. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts +6 -1
  7. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
  8. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +9 -2
  9. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
  10. package/dist/backend/backend/src/index.d.ts.map +1 -1
  11. package/dist/backend/backend/src/index.js +21 -0
  12. package/dist/backend/backend/src/index.js.map +1 -1
  13. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +43 -7
  14. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  15. package/dist/backend/backend/src/services/agent/agent-registration.service.js +158 -51
  16. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  17. package/dist/backend/backend/src/services/agent/registration-flow-registry.d.ts +92 -0
  18. package/dist/backend/backend/src/services/agent/registration-flow-registry.d.ts.map +1 -0
  19. package/dist/backend/backend/src/services/agent/registration-flow-registry.js +121 -0
  20. package/dist/backend/backend/src/services/agent/registration-flow-registry.js.map +1 -0
  21. package/dist/backend/backend/src/services/agent/resumed-kickoff.d.ts +66 -0
  22. package/dist/backend/backend/src/services/agent/resumed-kickoff.d.ts.map +1 -0
  23. package/dist/backend/backend/src/services/agent/resumed-kickoff.js +72 -0
  24. package/dist/backend/backend/src/services/agent/resumed-kickoff.js.map +1 -0
  25. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
  26. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js +9 -1
  27. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
  28. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
  29. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +8 -0
  30. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
  31. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts +14 -0
  32. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
  33. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js +53 -1
  34. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
  35. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +32 -0
  36. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  37. package/dist/backend/backend/src/services/task-pool/task-pool.service.js +60 -1
  38. package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  39. package/dist/backend/backend/src/types/v2/work-item.types.d.ts +37 -0
  40. package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  41. package/dist/backend/backend/src/types/v2/work-item.types.js +33 -0
  42. package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
  43. package/dist/backend/build-info.json +2 -2
  44. package/dist/cli/backend/src/constants.d.ts +4 -0
  45. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  46. package/dist/cli/backend/src/constants.js +4 -0
  47. package/dist/cli/backend/src/constants.js.map +1 -1
  48. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts +43 -7
  49. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  50. package/dist/cli/backend/src/services/agent/agent-registration.service.js +158 -51
  51. package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
  52. package/dist/cli/backend/src/services/agent/registration-flow-registry.d.ts +92 -0
  53. package/dist/cli/backend/src/services/agent/registration-flow-registry.d.ts.map +1 -0
  54. package/dist/cli/backend/src/services/agent/registration-flow-registry.js +121 -0
  55. package/dist/cli/backend/src/services/agent/registration-flow-registry.js.map +1 -0
  56. package/dist/cli/backend/src/services/agent/resumed-kickoff.d.ts +66 -0
  57. package/dist/cli/backend/src/services/agent/resumed-kickoff.d.ts.map +1 -0
  58. package/dist/cli/backend/src/services/agent/resumed-kickoff.js +72 -0
  59. package/dist/cli/backend/src/services/agent/resumed-kickoff.js.map +1 -0
  60. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts +14 -0
  61. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
  62. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js +53 -1
  63. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
  64. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +32 -0
  65. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  66. package/dist/cli/backend/src/services/task-pool/task-pool.service.js +60 -1
  67. package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  68. package/dist/cli/backend/src/types/v2/work-item.types.d.ts +37 -0
  69. package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  70. package/dist/cli/backend/src/types/v2/work-item.types.js +33 -0
  71. package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
  72. package/frontend/dist/assets/{index-97bb17d4.js → index-2c7ee4a6.js} +30 -30
  73. package/frontend/dist/index.html +1 -1
  74. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  import { CrewlyAgentExternalRuntimeService } from './crewly-agent/crewly-agent-external-runtime.service.js';
2
2
  import { StorageService } from '../core/storage.service.js';
3
3
  import { CREWLY_CONSTANTS, RuntimeType } from '../../constants.js';
4
+ import { type RegistrationFlowCancelReason } from './registration-flow-registry.js';
4
5
  export interface OrchestratorConfig {
5
6
  sessionName: string;
6
7
  projectPath: string;
@@ -55,7 +56,7 @@ export declare class AgentRegistrationService {
55
56
  private projectRoot;
56
57
  private promptCache;
57
58
  private sessionCreationLocks;
58
- private registrationAbortControllers;
59
+ private registrationFlows;
59
60
  private stuckMessageDetectorTimer;
60
61
  private tuiSessionRegistry;
61
62
  private sentMessageTracker;
@@ -87,11 +88,43 @@ export declare class AgentRegistrationService {
87
88
  }): void;
88
89
  /**
89
90
  * Cancel a pending registration prompt for a session.
90
- * Called by RuntimeExitMonitorService when a runtime exit is detected.
91
+ * Called by RuntimeExitMonitorService when a runtime exit is detected, and
92
+ * before every kill of the session's PTY.
91
93
  *
92
94
  * @param sessionName - The session whose registration to cancel
95
+ * @param reason - Why the flow is cancelled (defaults to a runtime exit)
93
96
  */
94
- cancelPendingRegistration(sessionName: string): void;
97
+ cancelPendingRegistration(sessionName: string, reason?: RegistrationFlowCancelReason): void;
98
+ /**
99
+ * The PTY currently registered under a session name, used as the identity
100
+ * a registration flow is bound to. Undefined when there is none (or the
101
+ * session helper is not initialised yet).
102
+ *
103
+ * @param sessionName - Session name
104
+ * @returns The live session object, compared by reference
105
+ */
106
+ private currentPtySession;
107
+ /**
108
+ * Whether a registration flow must stop writing to its session: its
109
+ * signal was aborted, a newer flow took over, or the session's PTY was
110
+ * killed/replaced since the flow started. Checked before every send.
111
+ *
112
+ * @param sessionName - Session the flow writes to
113
+ * @param abortSignal - The flow's signal (undefined = unguarded caller)
114
+ * @returns True when the caller must not send anything
115
+ */
116
+ private isFlowCancelled;
117
+ /**
118
+ * Whether a session is already up or being brought up: a creation is in
119
+ * flight, a PTY exists under the name, or an in-process runtime runs it.
120
+ * The boot restore uses this to leave alone an agent another launcher
121
+ * (reconciler wake, team start) already started this boot, instead of
122
+ * killing it and resuming the same conversation a second time.
123
+ *
124
+ * @param sessionName - Session name
125
+ * @returns True when the session is live or launching
126
+ */
127
+ isSessionLiveOrLaunching(sessionName: string): Promise<boolean>;
95
128
  /**
96
129
  * Get or create the session helper with lazy initialization
97
130
  */
@@ -257,13 +290,16 @@ export declare class AgentRegistrationService {
257
290
  */
258
291
  private noteResumedLaunch;
259
292
  /**
260
- * Kickoff text for a resumed (non-Claude) runtime: the backend restarted,
261
- * so its registration is gone even though the conversation remembers
262
- * doing it. Tell it plainly and hand it the command.
293
+ * Kickoff text for a resumed runtime conversation (any runtime): the
294
+ * backend restarted, so its registration is gone even though the
295
+ * conversation remembers doing it. Tell it plainly that this is the same
296
+ * conversation, hand it the exact register-self command its prompt uses
297
+ * (orchestrator skill for the orchestrator), and tell it to carry on.
263
298
  *
264
299
  * @param sessionName - The session
265
300
  * @param role - Its role
266
- * @param promptFilePath - The init prompt, for a context refresh if needed
301
+ * @param promptFilePath - The init prompt, for a context refresh; omitted
302
+ * for Claude Code, whose prompt is loaded as its agent definition
267
303
  * @returns The message to type into the runtime
268
304
  */
269
305
  private resumedKickoff;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-registration.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/agent/agent-registration.service.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,iCAAiC,EAAE,MAAM,yDAAyD,CAAC;AAK5G,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,OAAO,EACN,gBAAgB,EAMhB,WAAW,EAUX,MAAM,oBAAoB,CAAC;AAyE5B,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;aACnB,QAAQ,EAAE,OAAO;gBAAjB,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM;CAc/D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,IAAI,MAAM,CAQzF;AAED;;;;;;;;;GASG;AACH;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,qBAAa,wBAAwB;IACpC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,cAAc,CAAqC;IAC3D,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,WAAW,CAAS;IAG5B,OAAO,CAAC,WAAW,CAA6B;IAGhD,OAAO,CAAC,oBAAoB,CAAgI;IAG5J,OAAO,CAAC,4BAA4B,CAAsC;IAG1E,OAAO,CAAC,yBAAyB,CAA+C;IAGhF,OAAO,CAAC,kBAAkB,CAAkC;IAI5D,OAAO,CAAC,kBAAkB,CAKpB;IAKN,OAAO,CAAC,oBAAoB,CAAoC;IAIhE,OAAO,CAAC,mBAAmB,CAAuD;IAIlF,OAAO,CAAC,iBAAiB,CAAwD;IAEjF,0EAA0E;IAC1E,OAAO,CAAC,gBAAgB,CAA+E;IAEvG,qFAAqF;IACrF,OAAO,CAAC,eAAe,CAAmK;IAK1L,OAAO,CAAC,MAAM,KAAK,wBAAwB,GAE1C;gBAGA,kBAAkB,EAAE,OAAO,EAAE,+CAA+C;IAC5E,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,cAAc,EAAE,cAAc;IAyB/B;;;;OAIG;IACH,mBAAmB,CAAC,SAAS,EAAE;QAAE,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;KAAE,GAAG,IAAI;IAIpG;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE;QAAE,SAAS,CAAC,KAAK,EAAE,OAAO,gCAAgC,EAAE,uBAAuB,GAAG,OAAO,gCAAgC,EAAE,iBAAiB,CAAA;KAAE,GAAG,IAAI;IAItL;;;;;OAKG;IACH,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IASpD;;OAEG;YACW,gBAAgB;IA2B9B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAW5B;;;;;;;;;OASG;YACW,yBAAyB;IA+BvC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,4BAA4B;IAsDpC;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,OAAO,CAAC,6BAA6B;IA0KrC;;OAEG;IACH,OAAO,CAAC,eAAe;IAwBvB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,iBAAiB;IAUzB;;;;;;;;;OASG;YACW,mBAAmB;IA2DjC;;;;;;OAMG;YACW,iBAAiB;IAgB/B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAI5B;;;;;;OAMG;IACH,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,iCAAiC,GAAG,SAAS;IAIvF;;;;;;;;OAQG;IACH,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAKtD;;;;;;;;;;OAUG;YACW,mBAAmB;IA0DjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,6BAA6B;IA+CrC;;;;;;;;;;OAUG;IACH;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAKzB;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,+BAA+B;IAoCvC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAIxB;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;;OAGG;YACW,qBAAqB;IAkBnC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAOrC;IAEH;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAgB;IAEzD,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAqB;IAExD;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAC7D,8FAA8F;IAC9F,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAuD;IAExF;;;;;;;;;;OAUG;YACW,0BAA0B;IAqCxC;;;OAGG;YACW,oBAAoB;IAuBlC;;;OAGG;IACG,+BAA+B,CACpC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,GAAE,MAAoD,EAC7D,QAAQ,CAAC,EAAE,MAAM,EACjB,WAAW,GAAE,WAAuC,EACpD,YAAY,CAAC,EAAE,MAAM,EAAE,EACvB,wBAAwB,CAAC,EAAE,MAAM,EAAE,GACjC,OAAO,CAAC;QACV,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,gEAAgE;QAChE,SAAS,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IAqGF;;;OAGG;YACW,qBAAqB;IA8CnC;;;OAGG;YACW,mBAAmB;IAqJjC;;;;;;;;;;;OAWG;YACW,uBAAuB;IAmDrC;;;;;;;;;;;;;OAaG;YACW,0BAA0B;IA6DxC;;;;OAIG;YACW,2BAA2B;IAwDzC;;;;;;;;;;;;;OAaG;YACW,wBAAwB;IA0DtC;;;;;;;OAOG;IACH,OAAO,CAAC,0BAA0B;IAMlC;;;;;;;;;;;OAWG;YACW,uBAAuB;IAiDrC;;;OAGG;YACW,iBAAiB;IAiP/B;;;;;;;;;OASG;YACW,oBAAoB;IA0DlC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,0BAA0B;IAqDlC;;OAEG;IACH;;;;;;;OAOG;YACW,uBAAuB;YASvB,sBAAsB;IAsapC;;;;;;;;;;;OAWG;YACW,eAAe;IAsC7B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,qBAAqB;IAQ7B;;OAEG;YACW,mBAAmB;IA8BjC;;OAEG;YACW,sBAAsB;IAiCpC;;;;OAIG;YACW,mCAAmC;IAgJjD;;OAEG;YACW,uBAAuB;IA8BrC;;;;;;;;;OASG;IACG,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC;IA8B/C;;OAEG;YACW,yBAAyB;IAyBvC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,qBAAqB;IAY7B;;;;;;;;;;;;;;;;;;OAkBG;YACW,iBAAiB;IAmB/B;;;;OAIG;IACG,kBAAkB,CAAC,MAAM,EAAE;QAChC,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,WAAW,CAAC;QAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB;;;;WAIG;QACH,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB;;;;;WAKG;QACH,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;KACpC,GAAG,OAAO,CAAC;QACX,OAAO,EAAE,OAAO,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,gEAAgE;QAChE,SAAS,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IAkDF;;;;OAIG;YACW,uBAAuB;IA+lBrC;;;;;OAKG;IACG,qBAAqB,CAC1B,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE,MAAkB,GACtB,OAAO,CAAC;QACV,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IAuFF;;;;;;;OAOG;YACW,mBAAmB;IAYjC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,kBAAkB,CACvB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,WAAW,GACvB,OAAO,CAAC;QACV,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,OAAO,CAAC;KACjB,CAAC;IAqVF;;;;;;;;;;;OAWG;IACG,iBAAiB,CACtB,WAAW,EAAE,MAAM,EACnB,SAAS,GAAE,MAAqD,EAChE,WAAW,CAAC,EAAE,WAAW,GACvB,OAAO,CAAC,OAAO,CAAC;IAmHnB;;;;;;;;;OASG;YACW,oBAAoB;IA0zBlC;;;;;;;;;;OAUG;YACW,sBAAsB;IAmDpC;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,sBAAsB;IAyD9B;;;;;;;OAOG;YACW,sBAAsB;IA2BpC;;;;;;;;;;OAUG;IACH,yBAAyB,IAAI,IAAI;IAsBjC;;OAEG;IACH,wBAAwB,IAAI,IAAI;IAQhC;;;;;;;;OAQG;YACW,oBAAoB;IAqKlC;;;;;OAKG;IACH,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAK/C;;;;;;;OAOG;IACH,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAuB5D;;;;;;;;OAQG;IACH;;;;;;;;;;;;;;;OAeG;YACW,qBAAqB;IAiBnC,OAAO,CAAC,gBAAgB;IAkDxB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,mBAAmB;IA0B3B;;;;;;;;;OASG;YACW,qBAAqB;IA+BnC;;;;;OAKG;IACG,cAAc,CACnB,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,GACT,OAAO,CAAC;QACV,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IAyCF;;;;;;OAMG;IACG,gBAAgB,CACrB,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,MAAM,EACb,OAAO,GAAE,MAAa,GACpB,OAAO,CAAC;QACV,OAAO,EAAE,OAAO,CAAC;QACjB,IAAI,CAAC,EAAE;YACN,KAAK,EAAE;gBACN,WAAW,EAAE,MAAM,CAAC;gBACpB,IAAI,CAAC,EAAE,MAAM,CAAC;gBACd,OAAO,EAAE,OAAO,CAAC;gBACjB,MAAM,EAAE,CAAC,OAAO,gBAAgB,CAAC,cAAc,CAAC,CAAC,MAAM,OAAO,gBAAgB,CAAC,cAAc,CAAC,CAAC;aAC/F,CAAC;YACF,SAAS,EAAE,MAAM,CAAC;SAClB,CAAC;QACF,KAAK,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IAuCF;;;;;;;;;;;;;OAaG;YACW,kBAAkB;IA2LhC;;;;;;;;;OASG;IACG,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,SAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAW3E"}
1
+ {"version":3,"file":"agent-registration.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/agent/agent-registration.service.ts"],"names":[],"mappings":"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"}
@@ -35,6 +35,8 @@ import { synthesizeSlackConversationId } from '../chat-v2/legacy-dto.utils.js';
35
35
  import { isRuntimeStartupBlockedError } from './runtime-startup-blocked.error.js';
36
36
  import { buildHandoverSummary, claudeTranscriptPath, conversationExists, lastTurnContextTokens, orcFreshContextTokens, planRuntimeSessionFlags, waitForCodexSessionId, } from './runtime-session-recovery.js';
37
37
  import { getLocalApiBaseUrl } from '../../utils/local-api-url.utils.js';
38
+ import { RegistrationFlowRegistry } from './registration-flow-registry.js';
39
+ import { buildResumedKickoff } from './resumed-kickoff.js';
38
40
  /**
39
41
  * Whether a file exists (readable).
40
42
  *
@@ -122,8 +124,10 @@ export class AgentRegistrationService {
122
124
  promptCache = new Map();
123
125
  // Session creation locks to prevent concurrent createAgentSession calls for the same session
124
126
  sessionCreationLocks = new Map();
125
- // AbortControllers for pending registration prompts (keyed by session name)
126
- registrationAbortControllers = new Map();
127
+ // The one live registration/kickoff flow per session, bound to the PTY it
128
+ // was started for. An orphaned flow (its PTY killed and replaced under the
129
+ // same name) must never type into the replacement (2026-09-25).
130
+ registrationFlows = new RegistrationFlowRegistry();
127
131
  // Background stuck-message detector timer
128
132
  stuckMessageDetectorTimer = null;
129
133
  // Track TUI sessions for the stuck-message detector (sessionName → runtimeType)
@@ -187,16 +191,70 @@ export class AgentRegistrationService {
187
191
  }
188
192
  /**
189
193
  * Cancel a pending registration prompt for a session.
190
- * Called by RuntimeExitMonitorService when a runtime exit is detected.
194
+ * Called by RuntimeExitMonitorService when a runtime exit is detected, and
195
+ * before every kill of the session's PTY.
191
196
  *
192
197
  * @param sessionName - The session whose registration to cancel
198
+ * @param reason - Why the flow is cancelled (defaults to a runtime exit)
193
199
  */
194
- cancelPendingRegistration(sessionName) {
195
- const controller = this.registrationAbortControllers.get(sessionName);
196
- if (controller) {
197
- controller.abort();
198
- this.registrationAbortControllers.delete(sessionName);
199
- this.logger.info('Cancelled pending registration', { sessionName });
200
+ cancelPendingRegistration(sessionName, reason = 'runtime-exited') {
201
+ if (this.registrationFlows.cancel(sessionName, reason)) {
202
+ this.logger.info('Cancelled pending registration', { sessionName, reason });
203
+ }
204
+ }
205
+ /**
206
+ * The PTY currently registered under a session name, used as the identity
207
+ * a registration flow is bound to. Undefined when there is none (or the
208
+ * session helper is not initialised yet).
209
+ *
210
+ * @param sessionName - Session name
211
+ * @returns The live session object, compared by reference
212
+ */
213
+ currentPtySession(sessionName) {
214
+ try {
215
+ const helper = this._sessionHelper;
216
+ return typeof helper?.getSession === 'function' ? helper.getSession(sessionName) ?? undefined : undefined;
217
+ }
218
+ catch {
219
+ return undefined;
220
+ }
221
+ }
222
+ /**
223
+ * Whether a registration flow must stop writing to its session: its
224
+ * signal was aborted, a newer flow took over, or the session's PTY was
225
+ * killed/replaced since the flow started. Checked before every send.
226
+ *
227
+ * @param sessionName - Session the flow writes to
228
+ * @param abortSignal - The flow's signal (undefined = unguarded caller)
229
+ * @returns True when the caller must not send anything
230
+ */
231
+ isFlowCancelled(sessionName, abortSignal) {
232
+ const cancelled = this.registrationFlows.isCancelled(abortSignal, this.currentPtySession(sessionName));
233
+ if (cancelled && abortSignal && abortSignal.reason === 'session-replaced') {
234
+ this.logger.info('Registration flow orphaned — its PTY was replaced; sending nothing', { sessionName });
235
+ }
236
+ return cancelled;
237
+ }
238
+ /**
239
+ * Whether a session is already up or being brought up: a creation is in
240
+ * flight, a PTY exists under the name, or an in-process runtime runs it.
241
+ * The boot restore uses this to leave alone an agent another launcher
242
+ * (reconciler wake, team start) already started this boot, instead of
243
+ * killing it and resuming the same conversation a second time.
244
+ *
245
+ * @param sessionName - Session name
246
+ * @returns True when the session is live or launching
247
+ */
248
+ async isSessionLiveOrLaunching(sessionName) {
249
+ if (this.sessionCreationLocks.has(sessionName))
250
+ return true;
251
+ if (this.inProcessRuntimes.has(sessionName))
252
+ return true;
253
+ try {
254
+ return (await this.getSessionHelper()).sessionExists(sessionName);
255
+ }
256
+ catch {
257
+ return false;
200
258
  }
201
259
  }
202
260
  /**
@@ -818,21 +876,20 @@ export class AgentRegistrationService {
818
876
  this.resumedSessions.delete(sessionName);
819
877
  }
820
878
  /**
821
- * Kickoff text for a resumed (non-Claude) runtime: the backend restarted,
822
- * so its registration is gone even though the conversation remembers
823
- * doing it. Tell it plainly and hand it the command.
879
+ * Kickoff text for a resumed runtime conversation (any runtime): the
880
+ * backend restarted, so its registration is gone even though the
881
+ * conversation remembers doing it. Tell it plainly that this is the same
882
+ * conversation, hand it the exact register-self command its prompt uses
883
+ * (orchestrator skill for the orchestrator), and tell it to carry on.
824
884
  *
825
885
  * @param sessionName - The session
826
886
  * @param role - Its role
827
- * @param promptFilePath - The init prompt, for a context refresh if needed
887
+ * @param promptFilePath - The init prompt, for a context refresh; omitted
888
+ * for Claude Code, whose prompt is loaded as its agent definition
828
889
  * @returns The message to type into the runtime
829
890
  */
830
891
  resumedKickoff(sessionName, role, promptFilePath) {
831
- const skillsPath = path.join(this.projectRoot, 'config', 'skills', 'agent');
832
- return (`Crewly restarted and your registration was reset (you are "${sessionName}", role ${role}). ` +
833
- `Even if you registered earlier in this conversation, run this now: ` +
834
- `bash ${skillsPath}/core/register-self/execute.sh '{"sessionName":"${sessionName}","role":"${role}"}' ` +
835
- `— then re-read ${promptFilePath} only if you have lost your context, and continue where you left off.`);
892
+ return buildResumedKickoff({ projectRoot: this.projectRoot, sessionName, role, promptFilePath });
836
893
  }
837
894
  recordRuntimeSessionAfterLaunch(sessionName, runtimeType, cwd, launchedAtMs, plan) {
838
895
  if (runtimeType !== RUNTIME_TYPES.CODEX_CLI || plan.resumeSessionId || !cwd)
@@ -1393,12 +1450,18 @@ export class AgentRegistrationService {
1393
1450
  * runtime exits before registration completes.
1394
1451
  */
1395
1452
  async sendRegistrationPromptAsync(sessionName, role, memberId, runtimeType = RUNTIME_TYPES.CLAUDE_CODE) {
1396
- // Create AbortController for this registration
1397
- const controller = new AbortController();
1398
- this.registrationAbortControllers.set(sessionName, controller);
1453
+ // One live flow per session, bound to the PTY that exists right now
1454
+ // (the helper is initialised: the caller just created the PTY with it).
1455
+ // Starting it aborts any older flow for the same name.
1456
+ const { flow, superseded } = this.registrationFlows.begin(sessionName, this.currentPtySession(sessionName));
1457
+ if (superseded) {
1458
+ this.logger.info('Superseded an older registration flow for this session', { sessionName, generation: flow.generation });
1459
+ }
1460
+ const signal = flow.signal;
1461
+ const cancelled = () => this.isFlowCancelled(sessionName, signal);
1399
1462
  try {
1400
1463
  this.logger.info('Loading registration prompt', { sessionName, role, runtimeType });
1401
- if (controller.signal.aborted)
1464
+ if (cancelled())
1402
1465
  return;
1403
1466
  const prompt = await this.loadRegistrationPrompt(role, sessionName, memberId, runtimeType);
1404
1467
  this.logger.info('Registration prompt loaded, sending to agent', {
@@ -1409,12 +1472,12 @@ export class AgentRegistrationService {
1409
1472
  // while the TUI is still booting — with Codex the instruction landed
1410
1473
  // ~2 s after spawn, during `model: loading`, and was swallowed while
1411
1474
  // every log line still said "sent successfully" (server-install finding 3).
1412
- if (controller.signal.aborted)
1475
+ if (cancelled())
1413
1476
  return;
1414
- await this.waitForRuntimeInputReady(sessionName, runtimeType, controller.signal);
1415
- if (controller.signal.aborted)
1477
+ await this.waitForRuntimeInputReady(sessionName, runtimeType, signal);
1478
+ if (cancelled())
1416
1479
  return;
1417
- const sent = await this.sendPromptRobustly(sessionName, prompt, runtimeType, controller.signal);
1480
+ const sent = await this.sendPromptRobustly(sessionName, prompt, runtimeType, signal);
1418
1481
  if (sent) {
1419
1482
  this.logger.info('Registration prompt sent successfully', { sessionName, role });
1420
1483
  }
@@ -1424,11 +1487,11 @@ export class AgentRegistrationService {
1424
1487
  // Make a silent non-registration loud, and re-deliver once. Previously a
1425
1488
  // swallowed instruction left the agent at `started` / readyAt null forever
1426
1489
  // with no log line saying so.
1427
- await this.redeliverIfUnregistered(sessionName, role, prompt, runtimeType, controller.signal);
1490
+ await this.redeliverIfUnregistered(sessionName, role, prompt, runtimeType, signal);
1428
1491
  }
1429
1492
  catch (error) {
1430
- if (controller.signal.aborted) {
1431
- this.logger.info('Registration prompt cancelled (runtime exited)', { sessionName });
1493
+ if (cancelled()) {
1494
+ this.logger.info('Registration prompt cancelled', { sessionName, reason: String(signal.reason) });
1432
1495
  return;
1433
1496
  }
1434
1497
  this.logger.warn('Failed to send registration prompt asynchronously', {
@@ -1438,7 +1501,7 @@ export class AgentRegistrationService {
1438
1501
  });
1439
1502
  }
1440
1503
  finally {
1441
- this.registrationAbortControllers.delete(sessionName);
1504
+ this.registrationFlows.end(flow);
1442
1505
  }
1443
1506
  }
1444
1507
  /**
@@ -1465,7 +1528,7 @@ export class AgentRegistrationService {
1465
1528
  const pollMs = this.testAwareInterval(REGISTRATION_DELIVERY_CONSTANTS.RUNTIME_INPUT_READY_POLL_MS);
1466
1529
  const startedAt = Date.now();
1467
1530
  let polls = 0;
1468
- while (!abortSignal?.aborted) {
1531
+ while (!this.isFlowCancelled(sessionName, abortSignal)) {
1469
1532
  let screen = '';
1470
1533
  try {
1471
1534
  screen = sessionHelper.capturePane(sessionName);
@@ -1535,7 +1598,7 @@ export class AgentRegistrationService {
1535
1598
  for (let redelivery = 0; redelivery <= REGISTRATION_DELIVERY_CONSTANTS.MAX_REDELIVERIES; redelivery++) {
1536
1599
  const startedAt = Date.now();
1537
1600
  while (Date.now() - startedAt < timeout) {
1538
- if (abortSignal.aborted)
1601
+ if (this.isFlowCancelled(sessionName, abortSignal))
1539
1602
  return;
1540
1603
  if (await this.checkAgentRegistration(sessionName, role)) {
1541
1604
  this.logger.info('Agent registration confirmed after prompt delivery', {
@@ -1545,7 +1608,7 @@ export class AgentRegistrationService {
1545
1608
  }
1546
1609
  await delay(checkInterval);
1547
1610
  }
1548
- if (abortSignal.aborted)
1611
+ if (this.isFlowCancelled(sessionName, abortSignal))
1549
1612
  return;
1550
1613
  if (redelivery >= REGISTRATION_DELIVERY_CONSTANTS.MAX_REDELIVERIES) {
1551
1614
  this.logger.error('Agent never registered after registration prompt delivery', {
@@ -1563,7 +1626,7 @@ export class AgentRegistrationService {
1563
1626
  sessionName, role, runtimeType, timeoutMs: timeout,
1564
1627
  });
1565
1628
  await this.waitForRuntimeInputReady(sessionName, runtimeType, abortSignal);
1566
- if (abortSignal.aborted)
1629
+ if (this.isFlowCancelled(sessionName, abortSignal))
1567
1630
  return;
1568
1631
  const resent = await this.sendPromptRobustly(sessionName, prompt, runtimeType, abortSignal);
1569
1632
  this.logger.info('registration re-delivered', { sessionName, role, runtimeType, resent });
@@ -1578,6 +1641,7 @@ export class AgentRegistrationService {
1578
1641
  // for the old incarnation must not outlive it, or it false-declares the
1579
1642
  // replacement PTY dead before startMonitoring() below replaces it.
1580
1643
  RuntimeExitMonitorService.getInstance().stopMonitoring(sessionName);
1644
+ this.cancelPendingRegistration(sessionName, 'session-killed');
1581
1645
  await (await this.getSessionHelper()).killSession(sessionName);
1582
1646
  // Wait for cleanup
1583
1647
  await delay(1000);
@@ -2716,10 +2780,12 @@ Loop until done, blocked, or explicitly reassigned:
2716
2780
  error,
2717
2781
  };
2718
2782
  }
2719
- // If another creation is in progress for this session, wait for it
2783
+ // Single-flight per session name. A second caller while a creation is
2784
+ // in flight joins it (same result, one PTY, one kickoff flow) — team
2785
+ // start, reconciler wake and activateAgentBySession all land here.
2720
2786
  const existingLock = this.sessionCreationLocks.get(config.sessionName);
2721
- if (existingLock) {
2722
- this.logger.info('Waiting for existing session creation to complete', { sessionName: config.sessionName });
2787
+ if (existingLock && !config.forceRecreate) {
2788
+ this.logger.info('Joining in-flight session creation (single-flight)', { sessionName: config.sessionName });
2723
2789
  try {
2724
2790
  return await existingLock;
2725
2791
  }
@@ -2728,8 +2794,18 @@ Loop until done, blocked, or explicitly reassigned:
2728
2794
  this.logger.info('Previous session creation failed, proceeding with new attempt', { sessionName: config.sessionName });
2729
2795
  }
2730
2796
  }
2731
- // Create and store the lock promise
2732
- const creationPromise = this._createAgentSessionImpl(config);
2797
+ // forceRecreate does not join: it waits for the in-flight creation to
2798
+ // settle, then kills that session (aborting its registration flow) and
2799
+ // builds its own. Chaining through the lock — set synchronously below —
2800
+ // keeps it single-flight: later callers join the forceRecreate run, and
2801
+ // two creations for one name never run at the same time.
2802
+ const predecessor = existingLock && config.forceRecreate ? existingLock : undefined;
2803
+ if (predecessor) {
2804
+ this.logger.info('forceRecreate queued behind in-flight session creation', { sessionName: config.sessionName });
2805
+ }
2806
+ const creationPromise = predecessor
2807
+ ? predecessor.catch(() => undefined).then(() => this._createAgentSessionImpl(config))
2808
+ : this._createAgentSessionImpl(config);
2733
2809
  this.sessionCreationLocks.set(config.sessionName, creationPromise);
2734
2810
  try {
2735
2811
  const result = await creationPromise;
@@ -2867,6 +2943,11 @@ Loop until done, blocked, or explicitly reassigned:
2867
2943
  // judges the seconds-old replacement PTY (no runtime child yet)
2868
2944
  // dead and force-kills it — Step 1 then fails and Step 2 runs.
2869
2945
  RuntimeExitMonitorService.getInstance().stopMonitoring(sessionName);
2946
+ // The killed incarnation's registration flow must die with it:
2947
+ // stopMonitoring above means no exit callback will cancel it, and
2948
+ // left alive it would find the replacement PTY by name and type
2949
+ // a second kickoff into it (2026-09-25 startup-prompt loop).
2950
+ this.cancelPendingRegistration(sessionName, 'session-killed');
2870
2951
  const runtimeService = this.createRuntimeService(runtimeType);
2871
2952
  runtimeService.clearDetectionCache(sessionName);
2872
2953
  await (await this.getSessionHelper()).killSession(sessionName);
@@ -2880,6 +2961,20 @@ Loop until done, blocked, or explicitly reassigned:
2880
2961
  }
2881
2962
  }
2882
2963
  else {
2964
+ // A registration flow for this session is still running (e.g. the
2965
+ // boot restore is waiting for the resumed runtime): it will deliver
2966
+ // the kickoff. Sending a second one from here gave Sam two
2967
+ // "Crewly restarted" messages 80 s apart (2026-09-25, 1.20.135).
2968
+ if (this.registrationFlows.has(sessionName)) {
2969
+ this.logger.info('Session is already registering — joining the running flow, no second kickoff', {
2970
+ sessionName,
2971
+ });
2972
+ return {
2973
+ success: true,
2974
+ sessionName,
2975
+ message: 'Agent session is already starting; registration in progress',
2976
+ };
2977
+ }
2883
2978
  this.logger.info('Session already exists, attempting intelligent recovery instead of killing', {
2884
2979
  sessionName,
2885
2980
  });
@@ -2969,6 +3064,7 @@ Loop until done, blocked, or explicitly reassigned:
2969
3064
  this.logger.warn('All recovery attempts failed, falling back to session recreation', {
2970
3065
  sessionName,
2971
3066
  });
3067
+ this.cancelPendingRegistration(sessionName, 'session-killed');
2972
3068
  await (await this.getSessionHelper()).killSession(sessionName);
2973
3069
  await delay(1000); // Wait for cleanup
2974
3070
  }
@@ -3232,6 +3328,9 @@ Loop until done, blocked, or explicitly reassigned:
3232
3328
  this.logger.info('Terminating agent session (unified approach)', { sessionName, role });
3233
3329
  // Stop runtime exit monitoring before killing the session
3234
3330
  RuntimeExitMonitorService.getInstance().stopMonitoring(sessionName);
3331
+ // ...and with it the session's pending registration flow, which no
3332
+ // exit callback will cancel now.
3333
+ this.cancelPendingRegistration(sessionName, 'session-killed');
3235
3334
  // Stop OAuth relogin monitoring before killing the session
3236
3335
  OAuthReloginMonitorService.getInstance().stopMonitoring(sessionName);
3237
3336
  // Stop context window monitoring before killing the session
@@ -5218,13 +5317,19 @@ Loop until done, blocked, or explicitly reassigned:
5218
5317
  const handover = this.pendingHandovers.get(sessionName);
5219
5318
  if (handover)
5220
5319
  this.pendingHandovers.delete(sessionName);
5221
- const messageToSend = isClaudeCode
5222
- ? 'Begin your work now. Follow the step-by-step instructions in your agent definition EXACTLY — start with Step 1, then Step 2, then Step 3 (register-self). Do NOT skip or reorder steps. Registration is required before the system will deliver messages to you.' +
5223
- (handover
5224
- ? ` This is a fresh conversation: your previous one had grown to ${handover.tokens} tokens and was closed. After registering, read ${handover.path} once — it holds the end of what was said before.`
5225
- : '')
5226
- : resumedRole
5227
- ? this.resumedKickoff(sessionName, resumedRole, promptFilePath)
5320
+ // A resumed conversation (Claude `--resume`, Codex `resume`) already went
5321
+ // through its startup steps; the full kickoff would read as a restart
5322
+ // from scratch (2026-09-25). It gets the short "register again, then
5323
+ // carry on" message instead — for every runtime. A genuinely fresh
5324
+ // conversation (new session, or the orc handover case, which is never
5325
+ // marked resumed) keeps the full kickoff.
5326
+ const messageToSend = resumedRole
5327
+ ? this.resumedKickoff(sessionName, resumedRole, isClaudeCode ? undefined : promptFilePath)
5328
+ : isClaudeCode
5329
+ ? 'Begin your work now. Follow the step-by-step instructions in your agent definition EXACTLY — start with Step 1, then Step 2, then Step 3 (register-self). Do NOT skip or reorder steps. Registration is required before the system will deliver messages to you.' +
5330
+ (handover
5331
+ ? ` This is a fresh conversation: your previous one had grown to ${handover.tokens} tokens and was closed. After registering, read ${handover.path} once — it holds the end of what was said before.`
5332
+ : '')
5228
5333
  : `Read the file at ${promptFilePath} and follow all instructions in it.`;
5229
5334
  // Note: kickoff message is intentionally imperative for reliable agent bootstrapping.
5230
5335
  // The --agent flag (Claude Code) loads the prompt as trusted system context, so this
@@ -5241,7 +5346,7 @@ Loop until done, blocked, or explicitly reassigned:
5241
5346
  const maxAttempts = 1;
5242
5347
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
5243
5348
  // Check abort before each attempt
5244
- if (abortSignal?.aborted) {
5349
+ if (this.isFlowCancelled(sessionName, abortSignal)) {
5245
5350
  this.logger.info('Prompt delivery aborted (runtime exited)', { sessionName, attempt });
5246
5351
  return false;
5247
5352
  }
@@ -5254,7 +5359,7 @@ Loop until done, blocked, or explicitly reassigned:
5254
5359
  deliveryMethod: isClaudeCode ? 'system-prompt-kickoff' : 'file-read',
5255
5360
  });
5256
5361
  // Check abort before sending to terminal
5257
- if (abortSignal?.aborted) {
5362
+ if (this.isFlowCancelled(sessionName, abortSignal)) {
5258
5363
  this.logger.info('Prompt delivery aborted before send (runtime exited)', { sessionName });
5259
5364
  return false;
5260
5365
  }
@@ -5285,7 +5390,7 @@ Loop until done, blocked, or explicitly reassigned:
5285
5390
  }
5286
5391
  }
5287
5392
  // Check abort right before writing instruction to terminal
5288
- if (abortSignal?.aborted) {
5393
+ if (this.isFlowCancelled(sessionName, abortSignal)) {
5289
5394
  this.logger.info('Prompt delivery aborted before instruction send (runtime exited)', { sessionName });
5290
5395
  return false;
5291
5396
  }
@@ -5299,7 +5404,7 @@ Loop until done, blocked, or explicitly reassigned:
5299
5404
  if (isClaudeCode) {
5300
5405
  for (let i = 0; i < 24; i++) {
5301
5406
  await delay(1000);
5302
- if (abortSignal?.aborted)
5407
+ if (this.isFlowCancelled(sessionName, abortSignal))
5303
5408
  return false;
5304
5409
  const currentOutput = sessionHelper.capturePane(sessionName);
5305
5410
  // Processing indicators (spinners) = definitive success
@@ -5324,10 +5429,12 @@ Loop until done, blocked, or explicitly reassigned:
5324
5429
  // can be dropped). A missing agent costs far more than a duplicate kickoff
5325
5430
  // (idempotent: it just re-reads the prompt file), so resend ONCE and re-poll.
5326
5431
  this.logger.warn('Kickoff unconfirmed after 24s — resending once', { sessionName, runtimeType });
5432
+ if (this.isFlowCancelled(sessionName, abortSignal))
5433
+ return false;
5327
5434
  await sessionHelper.sendMessage(sessionName, messageToSend);
5328
5435
  for (let j = 0; j < 12; j++) {
5329
5436
  await delay(1000);
5330
- if (abortSignal?.aborted)
5437
+ if (this.isFlowCancelled(sessionName, abortSignal))
5331
5438
  return false;
5332
5439
  const out2 = sessionHelper.capturePane(sessionName);
5333
5440
  if (containsSpinnerOrWorkingIndicator(out2) || !this.isClaudeAtPrompt(out2, RUNTIME_TYPES.CLAUDE_CODE)) {