crewly 1.20.225 → 1.20.227

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 (95) hide show
  1. package/dist/backend/backend/src/constants.d.ts +11 -1
  2. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  3. package/dist/backend/backend/src/constants.js +11 -1
  4. package/dist/backend/backend/src/constants.js.map +1 -1
  5. package/dist/backend/backend/src/controllers/chat/agent-reply.controller.d.ts.map +1 -1
  6. package/dist/backend/backend/src/controllers/chat/agent-reply.controller.js +6 -0
  7. package/dist/backend/backend/src/controllers/chat/agent-reply.controller.js.map +1 -1
  8. package/dist/backend/backend/src/index.d.ts.map +1 -1
  9. package/dist/backend/backend/src/index.js +10 -0
  10. package/dist/backend/backend/src/index.js.map +1 -1
  11. package/dist/backend/backend/src/services/agent/claude-runtime.service.d.ts.map +1 -1
  12. package/dist/backend/backend/src/services/agent/claude-runtime.service.js +15 -0
  13. package/dist/backend/backend/src/services/agent/claude-runtime.service.js.map +1 -1
  14. package/dist/backend/backend/src/services/apps/app-thumbnail.service.d.ts.map +1 -1
  15. package/dist/backend/backend/src/services/apps/app-thumbnail.service.js +8 -2
  16. package/dist/backend/backend/src/services/apps/app-thumbnail.service.js.map +1 -1
  17. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +35 -0
  18. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
  19. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +64 -4
  20. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
  21. package/dist/backend/backend/src/services/decisions/decision.service.d.ts +6 -0
  22. package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -1
  23. package/dist/backend/backend/src/services/decisions/decision.service.js +29 -9
  24. package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -1
  25. package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.d.ts +9 -1
  26. package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.d.ts.map +1 -1
  27. package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.js +8 -2
  28. package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.js.map +1 -1
  29. package/dist/backend/backend/src/services/messaging/owner-message-watchdog.wiring.d.ts.map +1 -1
  30. package/dist/backend/backend/src/services/messaging/owner-message-watchdog.wiring.js +20 -2
  31. package/dist/backend/backend/src/services/messaging/owner-message-watchdog.wiring.js.map +1 -1
  32. package/dist/backend/backend/src/services/messaging/queue-priority.d.ts +127 -0
  33. package/dist/backend/backend/src/services/messaging/queue-priority.d.ts.map +1 -0
  34. package/dist/backend/backend/src/services/messaging/queue-priority.js +146 -0
  35. package/dist/backend/backend/src/services/messaging/queue-priority.js.map +1 -0
  36. package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +55 -3
  37. package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
  38. package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js +142 -10
  39. package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
  40. package/dist/backend/backend/src/services/open-items/open-items.service.d.ts +12 -0
  41. package/dist/backend/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
  42. package/dist/backend/backend/src/services/open-items/open-items.service.js +17 -2
  43. package/dist/backend/backend/src/services/open-items/open-items.service.js.map +1 -1
  44. package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
  45. package/dist/backend/backend/src/services/slack/slack-initializer.js +25 -0
  46. package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
  47. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +36 -0
  48. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  49. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +134 -5
  50. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  51. package/dist/backend/backend/src/services/slack/slack.service.d.ts +35 -4
  52. package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
  53. package/dist/backend/backend/src/services/slack/slack.service.js +95 -0
  54. package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
  55. package/dist/backend/build-info.json +2 -2
  56. package/dist/cli/backend/src/constants.d.ts +11 -1
  57. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  58. package/dist/cli/backend/src/constants.js +11 -1
  59. package/dist/cli/backend/src/constants.js.map +1 -1
  60. package/dist/cli/backend/src/services/agent/claude-runtime.service.d.ts.map +1 -1
  61. package/dist/cli/backend/src/services/agent/claude-runtime.service.js +15 -0
  62. package/dist/cli/backend/src/services/agent/claude-runtime.service.js.map +1 -1
  63. package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +35 -0
  64. package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
  65. package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +64 -4
  66. package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
  67. package/dist/cli/backend/src/services/decisions/decision.service.d.ts +6 -0
  68. package/dist/cli/backend/src/services/decisions/decision.service.d.ts.map +1 -1
  69. package/dist/cli/backend/src/services/decisions/decision.service.js +29 -9
  70. package/dist/cli/backend/src/services/decisions/decision.service.js.map +1 -1
  71. package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.d.ts +9 -1
  72. package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.d.ts.map +1 -1
  73. package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.js +8 -2
  74. package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.js.map +1 -1
  75. package/dist/cli/backend/src/services/messaging/queue-priority.d.ts +127 -0
  76. package/dist/cli/backend/src/services/messaging/queue-priority.d.ts.map +1 -0
  77. package/dist/cli/backend/src/services/messaging/queue-priority.js +146 -0
  78. package/dist/cli/backend/src/services/messaging/queue-priority.js.map +1 -0
  79. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +55 -3
  80. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
  81. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js +142 -10
  82. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
  83. package/dist/cli/backend/src/services/open-items/open-items.service.d.ts +12 -0
  84. package/dist/cli/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
  85. package/dist/cli/backend/src/services/open-items/open-items.service.js +17 -2
  86. package/dist/cli/backend/src/services/open-items/open-items.service.js.map +1 -1
  87. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +36 -0
  88. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  89. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +134 -5
  90. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  91. package/dist/cli/backend/src/services/slack/slack.service.d.ts +35 -4
  92. package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
  93. package/dist/cli/backend/src/services/slack/slack.service.js +95 -0
  94. package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
  95. package/package.json +1 -1
@@ -44,6 +44,7 @@ import { ROOM_RESPONDER_CONSTANTS } from '../../constants.js';
44
44
  import { buildRoomAgentDirectory, findPriorRoomAnswer, threadOwnerFromSlack, } from './room-responder.js';
45
45
  import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
46
46
  import { atomicWriteJson, safeReadJson } from '../../utils/file-io.utils.js';
47
+ import { getOwnerMessageWatchdog } from '../messaging/owner-message-watchdog.service.js';
47
48
  import { LoggerService } from '../core/logger.service.js';
48
49
  import { getSlackDirectoryService } from './slack-directory.service.js';
49
50
  import { SLACK_TEAM_CHANNEL_CONSTANTS, OWNER_EVIDENCE_METADATA, SLACK_THREAD_KEY_CONSTANTS } from '../../constants.js';
@@ -422,6 +423,7 @@ export class SlackTeamChannelService {
422
423
  void this.inviteInstalledEverywhere(record.agentSession);
423
424
  }) ?? null;
424
425
  this.started = true;
426
+ await this.restoreUnanswered();
425
427
  this.logger.info('Slack team channels started', {
426
428
  mappings: this.store?.mappings.length ?? 0,
427
429
  autoCreate: this.store?.autoCreate ?? true,
@@ -1772,10 +1774,15 @@ export class SlackTeamChannelService {
1772
1774
  const pinned = oneResponder?.pinned;
1773
1775
  const watchHere = pinned?.watchHere === true;
1774
1776
  const noteOnly = watchHere && pinned?.watchNoteOnly === true;
1775
- const answeredElsewhere = !!pinned && !pinned.session && !watchHere;
1776
- const watchable = watchHere || (!answeredElsewhere && !presence?.deferredElsewhere && !presence?.fallbackElsewhere);
1777
+ // A card answer consumed by the decision path for an asker on THIS
1778
+ // machine is not answered elsewhere: the asker only got a note, which
1779
+ // may sit on its queue while it is busy (2026-10-05, D-270: 18 min,
1780
+ // no watch). Watched here, so the fallback follows it up.
1781
+ const cardAsker = pinned?.reason === 'decision-consumed' && pinned.alreadyHas ? pinned.alreadyHas : null;
1782
+ const answeredElsewhere = !!pinned && !pinned.session && !watchHere && !cardAsker;
1783
+ const watchable = watchHere || !!cardAsker || (!answeredElsewhere && !presence?.deferredElsewhere && !presence?.fallbackElsewhere);
1777
1784
  if (!handoffTo && watchable && isOwnerAuthored(message, this.deps.getOwnerUserId?.())) {
1778
- this.watchUnanswered(message, mapping, persisted, dispatchOptions.threadId, recipients, heldBy, noteOnly);
1785
+ this.watchUnanswered(message, mapping, persisted, dispatchOptions.threadId, recipients, heldBy, noteOnly, cardAsker);
1779
1786
  }
1780
1787
  }
1781
1788
  return { mapping, message: persisted, mentions: resolved.mentions, dispatch };
@@ -2050,14 +2057,16 @@ export class SlackTeamChannelService {
2050
2057
  * @param persisted - The huddle row it was recorded as
2051
2058
  * @param threadId - Huddle thread the replies land in
2052
2059
  */
2053
- watchUnanswered(message, mapping, persisted, threadId, recipients = [], heldBy = [], noteOnly = false) {
2060
+ watchUnanswered(message, mapping, persisted, threadId, recipients = [], heldBy = [], noteOnly = false, decisionAsker = null) {
2054
2061
  const threadTs = message.threadTs || message.ts;
2055
2062
  const key = `${message.channelId}:${threadTs}`;
2056
2063
  this.settleUnanswered(key);
2064
+ const waitMs = noteOnly ? ROOM_RESPONDER_CONSTANTS.NOTE_ONLY_WATCH_MS : SLACK_TEAM_CHANNEL_CONSTANTS.ROOM_UNANSWERED_FALLBACK_MS;
2057
2065
  const timer = setTimeout(() => {
2058
2066
  void this.runUnansweredFallback(key);
2059
- }, noteOnly ? ROOM_RESPONDER_CONSTANTS.NOTE_ONLY_WATCH_MS : SLACK_TEAM_CHANNEL_CONSTANTS.ROOM_UNANSWERED_FALLBACK_MS);
2067
+ }, waitMs);
2060
2068
  timer.unref?.();
2069
+ const armedAt = Date.now();
2061
2070
  this.unanswered.set(key, {
2062
2071
  message,
2063
2072
  chatChannelId: mapping.chatChannelId,
@@ -2068,8 +2077,12 @@ export class SlackTeamChannelService {
2068
2077
  recipients,
2069
2078
  ...(heldBy.length > 0 ? { heldBy } : {}),
2070
2079
  ...(noteOnly ? { noteOnly: true } : {}),
2080
+ ...(decisionAsker ? { decisionAsker } : {}),
2081
+ armedAt,
2082
+ dueAt: armedAt + waitMs,
2071
2083
  timer,
2072
2084
  });
2085
+ this.persistUnanswered();
2073
2086
  }
2074
2087
  /** Stop waiting on a message: someone has it. */
2075
2088
  settleUnanswered(key) {
@@ -2079,6 +2092,73 @@ export class SlackTeamChannelService {
2079
2092
  if (pending.timer)
2080
2093
  clearTimeout(pending.timer);
2081
2094
  this.unanswered.delete(key);
2095
+ this.persistUnanswered();
2096
+ }
2097
+ /** @returns Where pending watches are kept across restarts */
2098
+ unansweredStorePath() {
2099
+ return this.deps.unansweredStorePath ?? path.join(path.dirname(this.storePath), SLACK_TEAM_CHANNEL_CONSTANTS.ROOM_UNANSWERED_STORE_FILENAME);
2100
+ }
2101
+ unansweredWrite = Promise.resolve();
2102
+ /**
2103
+ * Write the pending watches (those with a timer) to disk, serialised and
2104
+ * best-effort: a watch armed seconds before a restart must not vanish
2105
+ * with its timer.
2106
+ */
2107
+ persistUnanswered() {
2108
+ if (!this.started)
2109
+ return;
2110
+ const rows = [];
2111
+ for (const [key, p] of this.unanswered) {
2112
+ if (!p.timer)
2113
+ continue;
2114
+ const { timer: _timer, ...rest } = p;
2115
+ void _timer;
2116
+ const message = { ...rest.message };
2117
+ delete message.threadContext;
2118
+ rows.push({ ...rest, message, key, armedAt: p.armedAt ?? Date.now(), dueAt: p.dueAt ?? Date.now() });
2119
+ }
2120
+ const file = this.unansweredStorePath();
2121
+ this.unansweredWrite = this.unansweredWrite
2122
+ .then(async () => {
2123
+ await fs.mkdir(path.dirname(file), { recursive: true });
2124
+ await atomicWriteJson(file, { version: 1, watches: rows });
2125
+ })
2126
+ .catch((err) => this.logger.warn('Could not persist pending unanswered-message watches', { error: err instanceof Error ? err.message : String(err) }));
2127
+ }
2128
+ /**
2129
+ * Re-arm the watches pending when the process last stopped. Each fires at
2130
+ * its original due time, but no sooner than a grace period after boot;
2131
+ * one older than the restore limit is dropped (the owner-message
2132
+ * watchdog, also persisted, owns long waits).
2133
+ */
2134
+ async restoreUnanswered() {
2135
+ const raw = await safeReadJson(this.unansweredStorePath(), null).catch(() => null);
2136
+ const rows = Array.isArray(raw?.watches) ? raw.watches : [];
2137
+ const now = Date.now();
2138
+ let restored = 0;
2139
+ for (const row of rows) {
2140
+ if (!row || typeof row.key !== 'string' || !row.message || typeof row.armedAt !== 'number')
2141
+ continue;
2142
+ if (this.unanswered.has(row.key))
2143
+ continue;
2144
+ if (now - row.armedAt > SLACK_TEAM_CHANNEL_CONSTANTS.ROOM_UNANSWERED_RESTORE_MAX_AGE_MS) {
2145
+ this.logger.info('Dropped a pending unanswered-message watch from before the restart — too old', { key: row.key });
2146
+ continue;
2147
+ }
2148
+ const due = typeof row.dueAt === 'number' ? row.dueAt : row.armedAt + SLACK_TEAM_CHANNEL_CONSTANTS.ROOM_UNANSWERED_FALLBACK_MS;
2149
+ const delay = Math.max(due - now, SLACK_TEAM_CHANNEL_CONSTANTS.ROOM_UNANSWERED_RESTORE_GRACE_MS);
2150
+ const { key, ...entry } = row;
2151
+ const timer = setTimeout(() => {
2152
+ void this.runUnansweredFallback(key);
2153
+ }, delay);
2154
+ timer.unref?.();
2155
+ this.unanswered.set(key, { ...entry, timer });
2156
+ restored += 1;
2157
+ }
2158
+ if (restored > 0) {
2159
+ this.logger.info('Restored pending unanswered-message watches from before the restart', { restored });
2160
+ this.persistUnanswered();
2161
+ }
2082
2162
  }
2083
2163
  /**
2084
2164
  * Arm the route guard for an owner's room message that was just recorded
@@ -2256,6 +2336,7 @@ export class SlackTeamChannelService {
2256
2336
  if (!pending)
2257
2337
  return;
2258
2338
  this.unanswered.delete(key);
2339
+ this.persistUnanswered();
2259
2340
  try {
2260
2341
  // The chosen responder holds it on its queue (it was busy): it will
2261
2342
  // answer when idle; handing it to the lead too means two answers.
@@ -2280,6 +2361,10 @@ export class SlackTeamChannelService {
2280
2361
  });
2281
2362
  return;
2282
2363
  }
2364
+ if (pending.decisionAsker) {
2365
+ this.followUpCardAnswer(pending);
2366
+ return;
2367
+ }
2283
2368
  const mapping = this.findBySlackChannelId(pending.slackChannelId);
2284
2369
  // Not this machine's to hand over (another may be the watcher), or the
2285
2370
  // thread could not be read just now: tell the owner rather than hand
@@ -2327,6 +2412,50 @@ export class SlackTeamChannelService {
2327
2412
  });
2328
2413
  }
2329
2414
  }
2415
+ /**
2416
+ * The owner answered a card in its thread, the asker (here) got the
2417
+ * answer as a note, and nothing has been posted in the thread since. No
2418
+ * hand-off to the lead — the answer is the asker's to act on; the
2419
+ * owner-message watchdog now follows it up with the asker (a reminder at
2420
+ * its first deadline, then a note to the owner), and it survives a restart.
2421
+ *
2422
+ * @param pending - The watch that fell due
2423
+ */
2424
+ followUpCardAnswer(pending) {
2425
+ const asker = pending.decisionAsker;
2426
+ const tsMs = Math.round(Number.parseFloat(pending.message.ts) * 1000);
2427
+ const input = {
2428
+ surface: 'slack',
2429
+ slackChannelId: pending.slackChannelId,
2430
+ threadTs: pending.threadTs,
2431
+ sourceTs: pending.message.ts,
2432
+ chatChannelId: pending.chatChannelId,
2433
+ chatThreadId: pending.threadId,
2434
+ messageId: pending.messageId,
2435
+ responsible: asker,
2436
+ recipients: [asker],
2437
+ required: true,
2438
+ text: pending.message.text ?? '',
2439
+ ...(Number.isFinite(tsMs) && tsMs > 0 ? { receivedAt: tsMs } : {}),
2440
+ };
2441
+ this.logger.warn('Owner answered a decision card, and its asker has not replied in the thread yet — following it up', {
2442
+ slackChannel: pending.slackChannelId,
2443
+ ts: pending.message.ts,
2444
+ asker,
2445
+ });
2446
+ try {
2447
+ if (this.deps.trackOwnerMessage)
2448
+ this.deps.trackOwnerMessage(input);
2449
+ else
2450
+ getOwnerMessageWatchdog()?.track(input, { afterDecision: true });
2451
+ }
2452
+ catch (err) {
2453
+ this.logger.warn('Could not hand the card answer to the owner-message watchdog', {
2454
+ asker,
2455
+ error: err instanceof Error ? err.message : String(err),
2456
+ });
2457
+ }
2458
+ }
2330
2459
  /**
2331
2460
  * The member of a room that should pick up a message nobody took, among
2332
2461
  * the agents that run on this machine: whoever of them spoke there last