crewly 1.20.86 β†’ 1.20.87

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 (23) hide show
  1. package/config/skills/agent/core/reply-channel/SKILL.md +23 -0
  2. package/config/skills/agent/core/reply-channel/execute.sh +28 -0
  3. package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
  4. package/dist/backend/backend/src/controllers/slack/slack.controller.js +44 -0
  5. package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
  6. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +40 -0
  7. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
  8. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +61 -1
  9. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
  10. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +68 -5
  11. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  12. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +210 -78
  13. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  14. package/dist/backend/build-info.json +2 -2
  15. package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +40 -0
  16. package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
  17. package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +61 -1
  18. package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
  19. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +68 -5
  20. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  21. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +210 -78
  22. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  23. package/package.json +1 -1
@@ -66,7 +66,7 @@ export interface TeamChannelStorageApi {
66
66
  onStorageEvent(listener: (event: StorageEvent) => Promise<void> | void): () => void;
67
67
  }
68
68
  /** Dispatcher slice β€” resolved lazily because it is built after the bridge. */
69
- export type TeamChannelDispatcherApi = Pick<ChatV2DispatcherService, 'dispatchMessage'>;
69
+ export type TeamChannelDispatcherApi = Pick<ChatV2DispatcherService, 'dispatchMessage'> & Partial<Pick<ChatV2DispatcherService, 'planHuddleTargets'>>;
70
70
  /** Constructor dependencies. */
71
71
  export interface SlackTeamChannelServiceDeps {
72
72
  slack: TeamChannelSlackApi;
@@ -439,12 +439,46 @@ export declare class SlackTeamChannelService {
439
439
  * @returns The routing result, or null when the channel is not mapped
440
440
  */
441
441
  routeInbound(message: SlackIncomingMessage): Promise<RouteInboundResult | null>;
442
+ /**
443
+ * The local agent whose own app delivered this copy, if any.
444
+ *
445
+ * A direct message is never a room: an agent's DM is handled by the DM
446
+ * service, and anything that falls through from there belongs to the
447
+ * orchestrator path, not to an ad-hoc huddle.
448
+ *
449
+ * @param message - The inbound message
450
+ * @returns The agent's session name, or null
451
+ */
452
+ private localReceivingAgent;
453
+ /**
454
+ * Put πŸ‘€ on the message β€” one per agent that will receive it.
455
+ *
456
+ * It used to be a single reaction meaning "Crewly got this". The owner
457
+ * reads it as "how many agents saw this", and with several agents in a
458
+ * room that is the more useful answer: three agents who will each weigh a
459
+ * message should show three eyes, and an agent that was not handed the
460
+ * message should show none, so the count is honest rather than "everyone
461
+ * in the channel". Each agent reacts with its own bot.
462
+ *
463
+ * When nobody in particular receives it, or no receiving agent has a bot
464
+ * of its own, one reaction still goes on so the owner can see the message
465
+ * arrived β€” the 2026-09-21 lesson: no eyes reads as nothing arrived.
466
+ *
467
+ * @param message - The inbound message
468
+ * @param mapping - Its channel mapping
469
+ * @param mentions - Agents @'d in it
470
+ * @param planned - Who will receive it, from the dispatcher; null when unknown
471
+ */
472
+ private acknowledgeSeen;
442
473
  /**
443
474
  * Any Slack channel becomes routable the moment a local agent's bot is
444
- * @'d in it (the owner invited agents from different teams into a private
445
- * channel, say): a huddle is created for the channel with the @'d agents
446
- * as members and mapped under the synthetic team id `adhoc:<channel>`.
447
- * Messages that @ nobody local are left to the caller (orchestrator path).
475
+ * @'d in it, or a message reaches us through a local agent's own app
476
+ * (Slack only delivers to apps that are members, so that proves the bot
477
+ * is in the channel). The owner invited agents from different teams into
478
+ * a private channel, say: a huddle is created for the channel with those
479
+ * agents as members and mapped under the synthetic team id
480
+ * `adhoc:<channel>`. Anything else is left to the caller (orchestrator
481
+ * path).
448
482
  *
449
483
  * @param message - The inbound message
450
484
  * @returns The new mapping, or null when no local agent was @'d
@@ -474,6 +508,35 @@ export declare class SlackTeamChannelService {
474
508
  * `slackThreadTs`; failing that, the latest Slack-origin root in the
475
509
  * huddle; failing that, the channel top level.
476
510
  */
511
+ /**
512
+ * Show "<agent> is working on it…" in the thread, at the agent's request.
513
+ *
514
+ * Agents that must answer get this placeholder the moment the message
515
+ * arrives. Agents that were only *told* β€” a message nobody addressed to
516
+ * them, passed along so they can judge whether it concerns them β€” get none,
517
+ * because a placeholder promises a reply and most of them will not send
518
+ * one. When one of them does decide to answer, it calls this, so the owner
519
+ * sees who has taken the message on: two agents deciding to answer show two
520
+ * placeholders, which is exactly the signal the owner asked for.
521
+ *
522
+ * The key matches the one the reply is mirrored under, so the agent's
523
+ * actual answer replaces the placeholder rather than landing beside it.
524
+ *
525
+ * @param input - Chat channel and thread the agent is answering in
526
+ * @returns Whether a placeholder is now showing, or why not
527
+ */
528
+ beginWorkingForAgent(input: {
529
+ chatChannelId: string;
530
+ agentSession: string;
531
+ threadId?: string;
532
+ }): Promise<{
533
+ ok: true;
534
+ slackChannelId: string;
535
+ threadTs?: string;
536
+ } | {
537
+ ok: false;
538
+ reason: string;
539
+ }>;
477
540
  /**
478
541
  * Put a file into the Slack channel an agent is replying in.
479
542
  *
@@ -1 +1 @@
1
- {"version":3,"file":"slack-team-channel.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/slack/slack-team-channel.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAMH,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACpB,uBAAuB,EAEvB,gBAAgB,EACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EACV,uBAAuB,EACvB,qBAAqB,EACtB,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAQ/D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AACnF,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,uCAAuC,CAAC;AAM3F,mDAAmD;AACnD,MAAM,WAAW,mBAAmB;IAClC,WAAW,IAAI,OAAO,CAAC;IACvB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACvD,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvE,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IACpE,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,WAAW,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5D,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnG,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,UAAU,CAAC,OAAO,EAAE;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClC;AAED,2EAA2E;AAC3E,MAAM,MAAM,sBAAsB,GAAG,IAAI,CACvC,yBAAyB,EACzB,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,KAAK,GAAG,cAAc,GAAG,aAAa,GAAG,aAAa,CAC9F,CAAC;AAEF,oDAAoD;AACpD,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,aAAa,EACX,cAAc,GACd,kBAAkB,GAClB,qBAAqB,GACrB,yBAAyB,GACzB,qBAAqB,GACrB,qBAAqB,GACrB,qBAAqB,GACrB,YAAY,GACZ,IAAI,GACJ,KAAK,CACR,CAAC;AAEF,qDAAqD;AACrD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5B,cAAc,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,MAAM,IAAI,CAAC;CACrF;AAED,+EAA+E;AAC/E,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,uBAAuB,EAAE,iBAAiB,CAAC,CAAC;AAExF,gCAAgC;AAChC,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,mBAAmB,CAAC;IAC3B,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,qBAAqB,CAAC;IAC/B,+DAA+D;IAC/D,aAAa,EAAE,MAAM,wBAAwB,GAAG,IAAI,CAAC;IACrD;;;OAGG;IACH,UAAU,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAC3C,kEAAkE;IAClE,MAAM,CAAC,EAAE,IAAI,CAAC,6BAA6B,EAAE,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC;IAC/F,4FAA4F;IAC5F,YAAY,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,8FAA8F;IAC9F,YAAY,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACrC,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,qDAAqD;AACrD,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,8DAA8D;AAC9D,MAAM,WAAW,kBAAkB;IACjC,uCAAuC;IACvC,OAAO,EAAE,uBAAuB,CAAC;IACjC,qCAAqC;IACrC,OAAO,EAAE,cAAc,CAAC;IACxB,wCAAwC;IACxC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,mEAAmE;IACnE,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACvC,sEAAsE;IACtE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,CAWzE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,QAAQ,CAAC,GAAG,OAAO,CAExF;AAED;;;;;;;;;;;GAWG;AACH,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,eAAe,CAAC;AAElD,6EAA6E;AAC7E,eAAO,MAAM,yBAAyB,iBAAiB,CAAC;AAExD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEjE;AAKD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,CAAC,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,GAClB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAkBpF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,EAAE,CAI3D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,UAAU,GAAG,SAAS,EAC9B,WAAW,EAAE,MAAM,GAClB,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,CAAC,CAalE;AAED;;GAEG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA8B;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,OAAO,CAA+C;IAC9D,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,mBAAmB,CAA6B;IACxD,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqC;IAEjE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAE5B;IACF,OAAO,CAAC,OAAO,CAAS;IACxB,kFAAkF;IAClF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;gBAEpD,IAAI,EAAE,2BAA2B;IAW7C;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB5B;;;;;;;;;;;;;;;;OAgBG;YACW,oBAAoB;IAkBlC;;;;;;;OAOG;IACG,iBAAiB,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAuB1E,0BAA0B;IAC1B,IAAI,IAAI,IAAI;IAcZ,yEAAyE;YAC3D,IAAI;YAiBJ,IAAI;IAMlB;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,wBAAwB,CAAC;IAKtD;;;;;OAKG;IACG,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,wBAAwB,CAAC,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAoBjG;;;;;;;;;;OAUG;IACG,6BAA6B,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAwBnG;;;;OAIG;IACG,YAAY,IAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAKxD;;;;;;OAMG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,uBAAuB,GAAG,IAAI;IAI5D;;;;;OAKG;IACH,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,uBAAuB,GAAG,IAAI;IAI5E;;;;;OAKG;IACH,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,uBAAuB,GAAG,IAAI;IAI1E;;;;;OAKG;IACG,qBAAqB,IAAI,OAAO,CACpC,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAAA;KAAE,CAAC,CAC1G;IAaD;;;;;OAKG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IASnD;;;;;;;;;;OAUG;IACG,iBAAiB,CACrB,IAAI,EAAE,IAAI,EACV,OAAO,GAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAO,GACxC,OAAO,CAAC,uBAAuB,CAAC;IA4FnC;;;;;;;OAOG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,mBAAmB,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAyBnG;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAgC3F;;;;;;OAMG;IACG,eAAe,CACnB,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IAkBzD;;;;;OAKG;IACG,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAuC5D;;;;;;;;;;;;;;OAcG;IACG,YAAY,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IA0PrF;;;;;;;;;OASG;YACW,kBAAkB;IAoDhC;;;;;;;OAOG;IACG,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;IAsE3D;;;;;;;;OAQG;IACG,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IActD;;;;OAIG;IACH;;;;;;;;;;;;;;;;;OAiBG;IACG,kBAAkB,CAAC,KAAK,EAAE;QAC9B,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CACP;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,GAC7F;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAChC;IA4CD,OAAO,CAAC,uBAAuB;IAe/B;;;;;;;;;;OAUG;IACG,gBAAgB,CACpB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IA0D/F;;;;;OAKG;IACG,yBAAyB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAepE,iEAAiE;YACnD,SAAS;YAmBT,WAAW;YA0BX,sBAAsB;IA4BpC,yEAAyE;IACzE,OAAO,CAAC,UAAU;CAanB;AAmBD;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,GAAG,IAAI,CAExF;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,IAAI,uBAAuB,GAAG,IAAI,CAE3E"}
1
+ {"version":3,"file":"slack-team-channel.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/slack/slack-team-channel.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAMH,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACpB,uBAAuB,EAEvB,gBAAgB,EACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EACV,uBAAuB,EACvB,qBAAqB,EACtB,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAQ/D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AACnF,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,uCAAuC,CAAC;AAM3F,mDAAmD;AACnD,MAAM,WAAW,mBAAmB;IAClC,WAAW,IAAI,OAAO,CAAC;IACvB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACvD,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvE,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IACpE,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,WAAW,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5D,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnG,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,UAAU,CAAC,OAAO,EAAE;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClC;AAED,2EAA2E;AAC3E,MAAM,MAAM,sBAAsB,GAAG,IAAI,CACvC,yBAAyB,EACzB,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,KAAK,GAAG,cAAc,GAAG,aAAa,GAAG,aAAa,CAC9F,CAAC;AAEF,oDAAoD;AACpD,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,aAAa,EACX,cAAc,GACd,kBAAkB,GAClB,qBAAqB,GACrB,yBAAyB,GACzB,qBAAqB,GACrB,qBAAqB,GACrB,qBAAqB,GACrB,YAAY,GACZ,IAAI,GACJ,KAAK,CACR,CAAC;AAEF,qDAAqD;AACrD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5B,cAAc,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,MAAM,IAAI,CAAC;CACrF;AAED,+EAA+E;AAC/E,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,uBAAuB,EAAE,iBAAiB,CAAC,GACrF,OAAO,CAAC,IAAI,CAAC,uBAAuB,EAAE,mBAAmB,CAAC,CAAC,CAAC;AAE9D,gCAAgC;AAChC,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,mBAAmB,CAAC;IAC3B,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,qBAAqB,CAAC;IAC/B,+DAA+D;IAC/D,aAAa,EAAE,MAAM,wBAAwB,GAAG,IAAI,CAAC;IACrD;;;OAGG;IACH,UAAU,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAC3C,kEAAkE;IAClE,MAAM,CAAC,EAAE,IAAI,CAAC,6BAA6B,EAAE,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC;IAC/F,4FAA4F;IAC5F,YAAY,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,8FAA8F;IAC9F,YAAY,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACrC,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,qDAAqD;AACrD,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,8DAA8D;AAC9D,MAAM,WAAW,kBAAkB;IACjC,uCAAuC;IACvC,OAAO,EAAE,uBAAuB,CAAC;IACjC,qCAAqC;IACrC,OAAO,EAAE,cAAc,CAAC;IACxB,wCAAwC;IACxC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,mEAAmE;IACnE,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACvC,sEAAsE;IACtE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,CAWzE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,QAAQ,CAAC,GAAG,OAAO,CAExF;AAED;;;;;;;;;;;GAWG;AACH,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,eAAe,CAAC;AAElD,6EAA6E;AAC7E,eAAO,MAAM,yBAAyB,iBAAiB,CAAC;AAExD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEjE;AAKD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,CAAC,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,GAClB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAkBpF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,EAAE,CAI3D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,UAAU,GAAG,SAAS,EAC9B,WAAW,EAAE,MAAM,GAClB,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,CAAC,CAalE;AAED;;GAEG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA8B;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,OAAO,CAA+C;IAC9D,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,mBAAmB,CAA6B;IACxD,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqC;IAEjE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAE5B;IACF,OAAO,CAAC,OAAO,CAAS;IACxB,kFAAkF;IAClF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;gBAEpD,IAAI,EAAE,2BAA2B;IAW7C;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB5B;;;;;;;;;;;;;;;;OAgBG;YACW,oBAAoB;IAkBlC;;;;;;;OAOG;IACG,iBAAiB,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAuB1E,0BAA0B;IAC1B,IAAI,IAAI,IAAI;IAcZ,yEAAyE;YAC3D,IAAI;YAiBJ,IAAI;IAMlB;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,wBAAwB,CAAC;IAKtD;;;;;OAKG;IACG,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,wBAAwB,CAAC,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAoBjG;;;;;;;;;;OAUG;IACG,6BAA6B,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAwBnG;;;;OAIG;IACG,YAAY,IAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAKxD;;;;;;OAMG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,uBAAuB,GAAG,IAAI;IAI5D;;;;;OAKG;IACH,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,uBAAuB,GAAG,IAAI;IAI5E;;;;;OAKG;IACH,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,uBAAuB,GAAG,IAAI;IAI1E;;;;;OAKG;IACG,qBAAqB,IAAI,OAAO,CACpC,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAAA;KAAE,CAAC,CAC1G;IAaD;;;;;OAKG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IASnD;;;;;;;;;;OAUG;IACG,iBAAiB,CACrB,IAAI,EAAE,IAAI,EACV,OAAO,GAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAO,GACxC,OAAO,CAAC,uBAAuB,CAAC;IA4FnC;;;;;;;OAOG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,mBAAmB,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAyBnG;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAgC3F;;;;;;OAMG;IACG,eAAe,CACnB,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IAkBzD;;;;;OAKG;IACG,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAuC5D;;;;;;;;;;;;;;OAcG;IACG,YAAY,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAgOrF;;;;;;;;;OASG;YACW,mBAAmB;IAQjC;;;;;;;;;;;;;;;;;;OAkBG;YACW,eAAe;IAmE7B;;;;;;;;;;;;OAYG;YACW,kBAAkB;IAyDhC;;;;;;;OAOG;IACG,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;IAsE3D;;;;;;;;OAQG;IACG,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IActD;;;;OAIG;IACH;;;;;;;;;;;;;;;;OAgBG;IACG,oBAAoB,CAAC,KAAK,EAAE;QAChC,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAiCpG;;;;;;;;;;;;;;;;;OAiBG;IACG,kBAAkB,CAAC,KAAK,EAAE;QAC9B,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CACP;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,GAC7F;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAChC;IA4CD,OAAO,CAAC,uBAAuB;IAe/B;;;;;;;;;;OAUG;IACG,gBAAgB,CACpB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IA0D/F;;;;;OAKG;IACG,yBAAyB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAepE,iEAAiE;YACnD,SAAS;YAmBT,WAAW;YA0BX,sBAAsB;IA4BpC,yEAAyE;IACzE,OAAO,CAAC,UAAU;CAanB;AAmBD;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,GAAG,IAAI,CAExF;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,IAAI,uBAAuB,GAAG,IAAI,CAE3E"}
@@ -792,6 +792,18 @@ export class SlackTeamChannelService {
792
792
  mapping = await this.ensureAdhocChannel(message);
793
793
  if (!mapping)
794
794
  return null;
795
+ // Slack delivers a channel message to every app in the channel, so a copy
796
+ // that arrived through one of our agents' own apps proves that agent is
797
+ // in the room β€” whether or not anyone has ever @'d it there. An ad-hoc
798
+ // roster used to be "agents that were @'d here", which left an agent the
799
+ // owner had added to a private channel out of it until someone happened
800
+ // to address it by name.
801
+ const receiving = await this.localReceivingAgent(message);
802
+ if (receiving && isAdhocMapping(mapping) && !(mapping.members ?? []).includes(receiving)) {
803
+ mapping.members = [...(mapping.members ?? []), receiving];
804
+ this.deps.chat.setHuddleMembers(mapping.chatChannelId, mapping.members);
805
+ await this.save();
806
+ }
795
807
  // One Slack message can reach us twice with different event types
796
808
  // (`app_mention` for an @'d agent's app plus `message.channels`); the
797
809
  // team must see it once β€” a second copy is acknowledged, not dispatched.
@@ -880,58 +892,6 @@ export class SlackTeamChannelService {
880
892
  if (oldest !== undefined)
881
893
  this.seenInbound.delete(oldest);
882
894
  }
883
- // In an ad-hoc (often private) channel the master bot may not be a
884
- // member, so an agent's own bot reacts instead.
885
- //
886
- // Which agent's, though, has to be found out rather than assumed. This
887
- // used to take the first installed bot among the @'d agents and the
888
- // huddle roster, on the reasoning that a huddle member's bot is "by
889
- // definition in the channel". It is not: the roster grows as agents are
890
- // @'d and never shrinks, and a bot can be removed from a private channel
891
- // afterwards. Pick one that is not in the channel and Slack answers
892
- // `channel_not_found` β€” the message is routed, dispatched and answered,
893
- // and the owner sees no eyes at all (#daily-info, 2026-09-22).
894
- //
895
- // So try them in turn, @'d agents first since one of them is about to
896
- // reply, and fall back to the master bot last.
897
- const reactorTokens = isAdhocMapping(mapping)
898
- ? [
899
- ...new Set([...resolved.mentions, ...(mapping.members ?? [])]
900
- .map((m) => this.deps.identities?.getInstalled(m)?.botToken)
901
- .filter((t) => !!t)),
902
- undefined,
903
- ]
904
- : [undefined];
905
- // Swallowing this outright cost an evening: the owner saw no eyes and no
906
- // placeholder and reasonably concluded nothing had arrived, while the
907
- // message was in fact routed, dispatched and answered (2026-09-21).
908
- // Cosmetic, so still non-fatal β€” but never again silent.
909
- let reacted = false;
910
- let lastError = '';
911
- for (const token of reactorTokens) {
912
- try {
913
- await this.deps.slack.addReaction(message.channelId, message.ts, SLACK_TEAM_CHANNEL_CONSTANTS.INBOUND_REACTION, token);
914
- reacted = true;
915
- break;
916
- }
917
- catch (err) {
918
- const code = describeSlackError(err).code;
919
- lastError = code;
920
- // Only a not-a-member failure is worth trying another identity for.
921
- // Anything else (already reacted, rate limit, bad message ts) will
922
- // fail identically for every bot.
923
- if (code !== 'channel_not_found')
924
- break;
925
- }
926
- }
927
- if (!reacted) {
928
- this.logger.warn('Could not acknowledge the message with a reaction', {
929
- slackChannel: mapping.slackChannelName ?? message.channelId,
930
- adhoc: isAdhocMapping(mapping),
931
- identitiesTried: reactorTokens.length,
932
- error: lastError,
933
- });
934
- }
935
895
  // Ad-hoc channels grow their huddle as new agents get @'d there.
936
896
  if (isAdhocMapping(mapping) && resolved.mentions.length > 0) {
937
897
  const next = [...new Set([...(mapping.members ?? []), ...resolved.mentions])];
@@ -941,22 +901,45 @@ export class SlackTeamChannelService {
941
901
  await this.save();
942
902
  }
943
903
  }
944
- // @'d agents must reply: show the honest state in the thread for each
945
- // one that has its own bot β€” "waking up…" for an idle agent (a cold
946
- // start is 1–2 minutes), "is working on it…" once it holds the message.
947
- // Who will be asked to reply: the @'d members, or (nobody @'d, top-level
948
- // message) the team leader alone. Mirrors the dispatcher's targeting so
949
- // the placeholder matches who actually gets the message.
950
- const typingTargets = [];
951
- if (this.deps.typing) {
952
- let sessions = resolved.mentions;
953
- if (sessions.length === 0 && !message.threadTs && team) {
904
+ // Who will receive this, and who owes a reply β€” decided by the same rules
905
+ // delivery uses, but *before* delivery, which can take a minute or two
906
+ // when an agent has to be cold-started. The owner should not look at an
907
+ // unacknowledged message for that long.
908
+ const dispatcherForPlan = this.deps.getDispatcher();
909
+ const dispatchOptions = {
910
+ threadId: threadId ?? persisted.id,
911
+ replyVia: 'reply-channel',
912
+ // A local agent's own message (fanned out to the colleagues it @'d)
913
+ // must not come back to its author.
914
+ ...(remoteAgent ? { excludeSessions: [remoteAgent] } : {}),
915
+ };
916
+ const planned = dispatcherForPlan?.planHuddleTargets
917
+ ? await dispatcherForPlan.planHuddleTargets(channel, persisted, dispatchOptions).catch(() => null)
918
+ : null;
919
+ await this.acknowledgeSeen(message, mapping, resolved.mentions, planned);
920
+ // Agents that must reply get a placeholder straight away β€” "waking up…"
921
+ // for an idle agent (a cold start is 1–2 minutes), "is working on it…"
922
+ // once it holds the message. Agents that were only told, and may or may
923
+ // not decide to answer, get none: a placeholder is a promise of a reply,
924
+ // and they announce their own with `reply-channel --working` if they
925
+ // take it on. With a plan this covers the case the old heuristic missed β€”
926
+ // a bare follow-up in a thread, which the last speaker must answer.
927
+ let owing;
928
+ if (planned) {
929
+ owing = [...planned].filter(([, mode]) => mode === 'required').map(([session]) => session);
930
+ }
931
+ else {
932
+ owing = resolved.mentions;
933
+ if (owing.length === 0 && !message.threadTs && team) {
954
934
  // Same rule as the dispatcher's huddleLeaderFor: the team leader, else the first member.
955
935
  const leader = members.find((m) => String(m.role) === 'team-leader' || String(m.role) === 'tech-lead') ?? members[0];
956
936
  if (leader)
957
- sessions = [leader.sessionName];
937
+ owing = [leader.sessionName];
958
938
  }
959
- for (const session of sessions) {
939
+ }
940
+ const typingTargets = [];
941
+ if (this.deps.typing) {
942
+ for (const session of owing) {
960
943
  const member = members.find((m) => m.sessionName === session);
961
944
  const installed = this.deps.identities?.getInstalled(session);
962
945
  // Own bot when installed; otherwise the master bot wearing the agent's
@@ -984,12 +967,8 @@ export class SlackTeamChannelService {
984
967
  .join(' Β· ');
985
968
  }
986
969
  dispatch = await dispatcher.dispatchMessage(channel, persisted, {
987
- threadId: threadId ?? persisted.id,
988
- replyVia: 'reply-channel',
970
+ ...dispatchOptions,
989
971
  ...(roster ? { channelRoster: roster } : {}),
990
- // A local agent's own message (fanned out to the colleagues it @'d)
991
- // must not come back to its author.
992
- ...(remoteAgent ? { excludeSessions: [remoteAgent] } : {}),
993
972
  });
994
973
  }
995
974
  else {
@@ -1022,19 +1001,120 @@ export class SlackTeamChannelService {
1022
1001
  });
1023
1002
  return { mapping, message: persisted, mentions: resolved.mentions, dispatch };
1024
1003
  }
1004
+ /**
1005
+ * The local agent whose own app delivered this copy, if any.
1006
+ *
1007
+ * A direct message is never a room: an agent's DM is handled by the DM
1008
+ * service, and anything that falls through from there belongs to the
1009
+ * orchestrator path, not to an ad-hoc huddle.
1010
+ *
1011
+ * @param message - The inbound message
1012
+ * @returns The agent's session name, or null
1013
+ */
1014
+ async localReceivingAgent(message) {
1015
+ const session = message.agentSession;
1016
+ if (!session || message.channelId.startsWith('D'))
1017
+ return null;
1018
+ if (this.deps.isLocalAgent)
1019
+ return this.deps.isLocalAgent(session) ? session : null;
1020
+ const teams = await this.deps.storage.getTeams();
1021
+ return teams.some((t) => teamChannelMembers(t).some((m) => m.sessionName === session)) ? session : null;
1022
+ }
1023
+ /**
1024
+ * Put πŸ‘€ on the message β€” one per agent that will receive it.
1025
+ *
1026
+ * It used to be a single reaction meaning "Crewly got this". The owner
1027
+ * reads it as "how many agents saw this", and with several agents in a
1028
+ * room that is the more useful answer: three agents who will each weigh a
1029
+ * message should show three eyes, and an agent that was not handed the
1030
+ * message should show none, so the count is honest rather than "everyone
1031
+ * in the channel". Each agent reacts with its own bot.
1032
+ *
1033
+ * When nobody in particular receives it, or no receiving agent has a bot
1034
+ * of its own, one reaction still goes on so the owner can see the message
1035
+ * arrived β€” the 2026-09-21 lesson: no eyes reads as nothing arrived.
1036
+ *
1037
+ * @param message - The inbound message
1038
+ * @param mapping - Its channel mapping
1039
+ * @param mentions - Agents @'d in it
1040
+ * @param planned - Who will receive it, from the dispatcher; null when unknown
1041
+ */
1042
+ async acknowledgeSeen(message, mapping, mentions, planned) {
1043
+ const react = (token) => this.deps.slack.addReaction(message.channelId, message.ts, SLACK_TEAM_CHANNEL_CONSTANTS.INBOUND_REACTION, token);
1044
+ let seen = 0;
1045
+ const absent = [];
1046
+ for (const session of planned?.keys() ?? []) {
1047
+ const token = this.deps.identities?.getInstalled(session)?.botToken;
1048
+ if (!token)
1049
+ continue;
1050
+ try {
1051
+ await react(token);
1052
+ seen += 1;
1053
+ }
1054
+ catch (err) {
1055
+ const code = describeSlackError(err).code;
1056
+ if (code === 'channel_not_found' || code === 'not_in_channel')
1057
+ absent.push(session);
1058
+ }
1059
+ }
1060
+ if (absent.length > 0) {
1061
+ // The agent still gets the message; its bot is just not in the room
1062
+ // (removed from a private channel after it joined the roster, say).
1063
+ this.logger.info('Agent bot not in the channel β€” no eyes from it', {
1064
+ slackChannel: mapping.slackChannelName ?? message.channelId,
1065
+ agents: absent,
1066
+ });
1067
+ }
1068
+ if (seen > 0)
1069
+ return;
1070
+ // Nobody in particular, or nobody with a bot of their own. In an ad-hoc
1071
+ // (often private) channel the master bot may not be a member, so try the
1072
+ // agents' bots β€” @'d ones first β€” and the master bot last. Only a
1073
+ // not-a-member failure is worth another identity: already-reacted, rate
1074
+ // limits and a bad ts fail identically for every bot.
1075
+ const fallback = isAdhocMapping(mapping)
1076
+ ? [
1077
+ ...new Set([...mentions, ...(mapping.members ?? [])]
1078
+ .map((m) => this.deps.identities?.getInstalled(m)?.botToken)
1079
+ .filter((t) => !!t)),
1080
+ undefined,
1081
+ ]
1082
+ : [undefined];
1083
+ let lastError = '';
1084
+ for (const token of fallback) {
1085
+ try {
1086
+ await react(token);
1087
+ return;
1088
+ }
1089
+ catch (err) {
1090
+ lastError = describeSlackError(err).code;
1091
+ if (lastError !== 'channel_not_found')
1092
+ break;
1093
+ }
1094
+ }
1095
+ // Swallowing this outright cost an evening (2026-09-21): cosmetic, so
1096
+ // non-fatal, but never silent.
1097
+ this.logger.warn('Could not acknowledge the message with a reaction', {
1098
+ slackChannel: mapping.slackChannelName ?? message.channelId,
1099
+ adhoc: isAdhocMapping(mapping),
1100
+ identitiesTried: fallback.length,
1101
+ error: lastError,
1102
+ });
1103
+ }
1025
1104
  /**
1026
1105
  * Any Slack channel becomes routable the moment a local agent's bot is
1027
- * @'d in it (the owner invited agents from different teams into a private
1028
- * channel, say): a huddle is created for the channel with the @'d agents
1029
- * as members and mapped under the synthetic team id `adhoc:<channel>`.
1030
- * Messages that @ nobody local are left to the caller (orchestrator path).
1106
+ * @'d in it, or a message reaches us through a local agent's own app
1107
+ * (Slack only delivers to apps that are members, so that proves the bot
1108
+ * is in the channel). The owner invited agents from different teams into
1109
+ * a private channel, say: a huddle is created for the channel with those
1110
+ * agents as members and mapped under the synthetic team id
1111
+ * `adhoc:<channel>`. Anything else is left to the caller (orchestrator
1112
+ * path).
1031
1113
  *
1032
1114
  * @param message - The inbound message
1033
1115
  * @returns The new mapping, or null when no local agent was @'d
1034
1116
  */
1035
1117
  async ensureAdhocChannel(message) {
1036
- if (!message.text || !message.text.includes('<@') && !message.text.includes('@'))
1037
- return null;
1038
1118
  const teams = await this.deps.storage.getTeams();
1039
1119
  const members = teams.flatMap((t) => teamChannelMembers(t));
1040
1120
  if (members.length === 0)
@@ -1049,10 +1129,17 @@ export class SlackTeamChannelService {
1049
1129
  // Only a real Slack mention of an agent's bot user counts here β€” a bare
1050
1130
  // "@name" in some unrelated channel must not hijack it.
1051
1131
  const botIds = new Set(candidates.map((c) => c.botUserId).filter((id) => !!id));
1052
- const mentionedBots = [...message.text.matchAll(/<@([A-Z0-9]+)>/g)].map((m) => m[1]).filter((id) => botIds.has(id));
1053
- if (mentionedBots.length === 0)
1054
- return null;
1132
+ const text = message.text ?? '';
1133
+ const mentionedBots = [...text.matchAll(/<@([A-Z0-9]+)>/g)].map((m) => m[1]).filter((id) => botIds.has(id));
1055
1134
  const sessions = candidates.filter((c) => c.botUserId && mentionedBots.includes(c.botUserId)).map((c) => c.sessionName);
1135
+ // The copy came through a local agent's own app: that agent's bot is in
1136
+ // this channel, which makes the channel a room it belongs to even if
1137
+ // nobody has addressed it yet.
1138
+ const receiving = await this.localReceivingAgent(message);
1139
+ if (receiving && !sessions.includes(receiving))
1140
+ sessions.push(receiving);
1141
+ if (sessions.length === 0)
1142
+ return null;
1056
1143
  // The master bot is usually not in this channel (private); the name is
1057
1144
  // best-effort and falls back to the id.
1058
1145
  const info = await this.deps.slack.getChannelInfo(message.channelId).catch(() => null);
@@ -1190,6 +1277,51 @@ export class SlackTeamChannelService {
1190
1277
  * `slackThreadTs`; failing that, the latest Slack-origin root in the
1191
1278
  * huddle; failing that, the channel top level.
1192
1279
  */
1280
+ /**
1281
+ * Show "<agent> is working on it…" in the thread, at the agent's request.
1282
+ *
1283
+ * Agents that must answer get this placeholder the moment the message
1284
+ * arrives. Agents that were only *told* β€” a message nobody addressed to
1285
+ * them, passed along so they can judge whether it concerns them β€” get none,
1286
+ * because a placeholder promises a reply and most of them will not send
1287
+ * one. When one of them does decide to answer, it calls this, so the owner
1288
+ * sees who has taken the message on: two agents deciding to answer show two
1289
+ * placeholders, which is exactly the signal the owner asked for.
1290
+ *
1291
+ * The key matches the one the reply is mirrored under, so the agent's
1292
+ * actual answer replaces the placeholder rather than landing beside it.
1293
+ *
1294
+ * @param input - Chat channel and thread the agent is answering in
1295
+ * @returns Whether a placeholder is now showing, or why not
1296
+ */
1297
+ async beginWorkingForAgent(input) {
1298
+ const mapping = (this.store?.mappings ?? []).find((m) => m.chatChannelId === input.chatChannelId);
1299
+ if (!mapping)
1300
+ return { ok: false, reason: 'not_a_slack_channel' };
1301
+ if (!this.deps.typing)
1302
+ return { ok: false, reason: 'placeholders_unavailable' };
1303
+ if (!this.deps.slack.isConnected())
1304
+ return { ok: false, reason: 'slack_not_connected' };
1305
+ const threadTs = this.resolveOutboundThreadTs(mapping, {
1306
+ channelId: input.chatChannelId,
1307
+ senderId: input.agentSession,
1308
+ ...(input.threadId ? { threadId: input.threadId } : {}),
1309
+ });
1310
+ const team = (await this.deps.storage.getTeams()).find((t) => (t.members ?? []).some((m) => m.sessionName === input.agentSession));
1311
+ const member = team?.members.find((m) => m.sessionName === input.agentSession);
1312
+ const installed = this.deps.identities?.getInstalled(input.agentSession) ?? null;
1313
+ const displayName = member?.name ?? input.agentSession;
1314
+ const identity = installed
1315
+ ? { botToken: installed.botToken, displayName }
1316
+ : { displayName, ...slackIdentityFor(member, input.agentSession) };
1317
+ await this.deps.typing.begin({ agentSession: input.agentSession, slackChannelId: mapping.slackChannelId, ...(threadTs ? { threadTs } : {}) }, identity, 'typing');
1318
+ this.logger.info('Agent took a message on', {
1319
+ agentSession: input.agentSession,
1320
+ slackChannel: mapping.slackChannelName,
1321
+ threaded: Boolean(threadTs),
1322
+ });
1323
+ return { ok: true, slackChannelId: mapping.slackChannelId, ...(threadTs ? { threadTs } : {}) };
1324
+ }
1193
1325
  /**
1194
1326
  * Put a file into the Slack channel an agent is replying in.
1195
1327
  *