crewly 1.20.228 → 1.20.230

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 (66) hide show
  1. package/config/skills/agent/core/app-comments/SKILL.md +39 -5
  2. package/config/skills/agent/core/app-comments/execute.sh +9 -4
  3. package/config/skills/agent/core/app-comments/execute.test.sh +9 -0
  4. package/config/skills/agent/core/publish-app/SKILL.md +3 -1
  5. package/config/skills/agent/core/publish-app/execute.sh +14 -1
  6. package/config/skills/agent/core/publish-app/execute.test.sh +7 -0
  7. package/config/skills/orchestrator/transfer-app/SKILL.md +40 -0
  8. package/config/skills/orchestrator/transfer-app/execute.sh +16 -0
  9. package/dist/backend/backend/src/constants.d.ts +29 -0
  10. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  11. package/dist/backend/backend/src/constants.js +29 -0
  12. package/dist/backend/backend/src/constants.js.map +1 -1
  13. package/dist/backend/backend/src/controllers/apps/apps.controller.d.ts +2 -0
  14. package/dist/backend/backend/src/controllers/apps/apps.controller.d.ts.map +1 -1
  15. package/dist/backend/backend/src/controllers/apps/apps.controller.js +6 -0
  16. package/dist/backend/backend/src/controllers/apps/apps.controller.js.map +1 -1
  17. package/dist/backend/backend/src/controllers/apps/apps.routes.d.ts +1 -0
  18. package/dist/backend/backend/src/controllers/apps/apps.routes.d.ts.map +1 -1
  19. package/dist/backend/backend/src/controllers/apps/apps.routes.js +3 -1
  20. package/dist/backend/backend/src/controllers/apps/apps.routes.js.map +1 -1
  21. package/dist/backend/backend/src/services/apps/app-roster.service.d.ts +86 -0
  22. package/dist/backend/backend/src/services/apps/app-roster.service.d.ts.map +1 -0
  23. package/dist/backend/backend/src/services/apps/app-roster.service.js +119 -0
  24. package/dist/backend/backend/src/services/apps/app-roster.service.js.map +1 -0
  25. package/dist/backend/backend/src/services/apps/app-wake-message.d.ts +24 -1
  26. package/dist/backend/backend/src/services/apps/app-wake-message.d.ts.map +1 -1
  27. package/dist/backend/backend/src/services/apps/app-wake-message.js +62 -5
  28. package/dist/backend/backend/src/services/apps/app-wake-message.js.map +1 -1
  29. package/dist/backend/backend/src/services/apps/app-wake.service.d.ts +56 -0
  30. package/dist/backend/backend/src/services/apps/app-wake.service.d.ts.map +1 -1
  31. package/dist/backend/backend/src/services/apps/app-wake.service.js +211 -8
  32. package/dist/backend/backend/src/services/apps/app-wake.service.js.map +1 -1
  33. package/dist/backend/backend/src/services/apps/apps-registry.service.d.ts +33 -0
  34. package/dist/backend/backend/src/services/apps/apps-registry.service.d.ts.map +1 -1
  35. package/dist/backend/backend/src/services/apps/apps-registry.service.js +44 -0
  36. package/dist/backend/backend/src/services/apps/apps-registry.service.js.map +1 -1
  37. package/dist/backend/backend/src/services/apps/apps.service.d.ts +88 -0
  38. package/dist/backend/backend/src/services/apps/apps.service.d.ts.map +1 -1
  39. package/dist/backend/backend/src/services/apps/apps.service.js +106 -6
  40. package/dist/backend/backend/src/services/apps/apps.service.js.map +1 -1
  41. package/dist/backend/backend/src/services/apps/apps.wiring.d.ts +21 -1
  42. package/dist/backend/backend/src/services/apps/apps.wiring.d.ts.map +1 -1
  43. package/dist/backend/backend/src/services/apps/apps.wiring.js +46 -1
  44. package/dist/backend/backend/src/services/apps/apps.wiring.js.map +1 -1
  45. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +5 -3
  46. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  47. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +48 -22
  48. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  49. package/dist/backend/backend/src/services/slack/slack-thread-context.service.d.ts +1 -1
  50. package/dist/backend/backend/src/services/slack/slack-thread-context.service.d.ts.map +1 -1
  51. package/dist/backend/backend/src/services/slack/slack-thread-context.service.js +9 -4
  52. package/dist/backend/backend/src/services/slack/slack-thread-context.service.js.map +1 -1
  53. package/dist/backend/build-info.json +2 -2
  54. package/dist/cli/backend/src/constants.d.ts +29 -0
  55. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  56. package/dist/cli/backend/src/constants.js +29 -0
  57. package/dist/cli/backend/src/constants.js.map +1 -1
  58. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +5 -3
  59. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  60. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +48 -22
  61. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  62. package/dist/cli/backend/src/services/slack/slack-thread-context.service.d.ts +1 -1
  63. package/dist/cli/backend/src/services/slack/slack-thread-context.service.d.ts.map +1 -1
  64. package/dist/cli/backend/src/services/slack/slack-thread-context.service.js +9 -4
  65. package/dist/cli/backend/src/services/slack/slack-thread-context.service.js.map +1 -1
  66. package/package.json +1 -1
@@ -1864,14 +1864,28 @@ export class SlackTeamChannelService {
1864
1864
  const read = await this.slackThreadOwner(message, members, roomSessions, isLocal);
1865
1865
  if (!read.readable) {
1866
1866
  // Machines that cannot read the thread would each guess from their
1867
- // own log: only the room's owner machine does — and only when its log
1868
- // can be trusted to name the last speaker. Otherwise it answers
1869
- // nothing and keeps the 90 s watch.
1867
+ // own log: only the room's owner machine does. It answers by the
1868
+ // room's rules (the log's last local speaker, else an awake member or
1869
+ // the lead) unless its log shows a colleague on another machine spoke
1870
+ // last — that machine may answer, and the 90 s watch backs it up.
1871
+ // A stale or empty log no longer means "nobody": awake agents here
1872
+ // were left silent and the owner got "nobody was awake"
1873
+ // (2026-10-05, #book-publish).
1870
1874
  if (!ownsRoom)
1871
1875
  return deferToRoomOwner;
1872
1876
  if (!room)
1873
1877
  return base;
1874
- return this.localThreadLogTrusted(mapping.chatChannelId, threadId, messageId, room, me) ? base : watchOnly;
1878
+ const log = this.localThreadLog(mapping.chatChannelId, threadId, messageId, room, me);
1879
+ if (log === 'remote-last')
1880
+ return watchOnly;
1881
+ if (log !== 'trusted') {
1882
+ this.logger.info('Slack thread unreadable — the room owner answers by its own rules', {
1883
+ slackChannel: mapping.slackChannelName ?? message.channelId,
1884
+ ts: message.ts,
1885
+ log,
1886
+ });
1887
+ }
1888
+ return base;
1875
1889
  }
1876
1890
  if (!read.owner)
1877
1891
  return base;
@@ -1902,7 +1916,7 @@ export class SlackTeamChannelService {
1902
1916
  return base;
1903
1917
  }
1904
1918
  /**
1905
- * Whether this machine's chat log can name a thread's last speaker when
1919
+ * What this machine's chat log can say about a thread's last speaker when
1906
1920
  * Slack cannot be read. Cloud never forwards other machines' bot posts, so
1907
1921
  * with colleagues on other machines in the room the log is trusted only
1908
1922
  * when its latest agent turn in the thread is local and recent.
@@ -1912,14 +1926,16 @@ export class SlackTeamChannelService {
1912
1926
  * @param messageId - The message being routed (not counted)
1913
1927
  * @param room - Cloud's room presence
1914
1928
  * @param me - This machine's instance id, when known
1915
- * @returns True when local rules may answer from the log
1929
+ * @returns `trusted` when local rules may answer from the log; `remote-last`
1930
+ * when the log shows a colleague on another machine spoke (or was asked)
1931
+ * last; `stale` / `unknown` when the log cannot say
1916
1932
  */
1917
- localThreadLogTrusted(chatChannelId, threadId, messageId, room, me) {
1933
+ localThreadLog(chatChannelId, threadId, messageId, room, me) {
1918
1934
  const othersInRoom = room.members.some((m) => !me || m.instanceId !== me);
1919
1935
  if (!othersInRoom)
1920
- return true;
1936
+ return 'trusted';
1921
1937
  if (!threadId || !this.deps.chat.listThreadForBridge)
1922
- return false;
1938
+ return 'unknown';
1923
1939
  const rows = this.deps.chat.listThreadForBridge(chatChannelId, threadId).filter((r) => r.id !== messageId);
1924
1940
  const isLocal = (s) => this.deps.isLocalAgent?.(localAgentSession(s)) ?? false;
1925
1941
  // A person @'d an agent on another machine: that agent's answer (no @
@@ -1933,16 +1949,16 @@ export class SlackTeamChannelService {
1933
1949
  for (let i = rows.length - 1; i >= 0; i--) {
1934
1950
  const r = rows[i];
1935
1951
  if (askedElsewhere(r))
1936
- return false;
1952
+ return 'remote-last';
1937
1953
  const remote = r.senderType === 'user' && typeof r.metadata?.[OWNER_EVIDENCE_METADATA.REMOTE_AGENT_SESSION] === 'string';
1938
1954
  if (r.senderType !== 'agent' && !remote)
1939
1955
  continue;
1940
1956
  if (remote)
1941
- return false;
1957
+ return 'remote-last';
1942
1958
  const now = this.deps.now ? this.deps.now().getTime() : Date.now();
1943
- return now - r.createdAt <= ROOM_RESPONDER_CONSTANTS.LOCAL_LOG_FRESH_MS;
1959
+ return now - r.createdAt <= ROOM_RESPONDER_CONSTANTS.LOCAL_LOG_FRESH_MS ? 'trusted' : 'stale';
1944
1960
  }
1945
- return false;
1961
+ return 'unknown';
1946
1962
  }
1947
1963
  /**
1948
1964
  * Record on the owner's row who was chosen to answer it — the reply gate
@@ -2350,9 +2366,12 @@ export class SlackTeamChannelService {
2350
2366
  }
2351
2367
  // An agent — here or on another machine — already replied in the
2352
2368
  // thread, or shows "working on it": nothing to hand over.
2353
- const after = this.deps.slackRepliesAfter
2354
- ? await withinMs(this.deps.slackRepliesAfter(pending.slackChannelId, pending.threadTs, pending.message.ts).catch(() => null), ROOM_RESPONDER_CONSTANTS.THREAD_CONTEXT_WAIT_MS, null)
2355
- : null;
2369
+ // Nobody waits on this read, so it gets a longer budget and one retry
2370
+ // (a busy backend missed the router's 3 s regularly).
2371
+ const readAfter = () => this.deps.slackRepliesAfter
2372
+ ? withinMs(this.deps.slackRepliesAfter(pending.slackChannelId, pending.threadTs, pending.message.ts).catch(() => null), ROOM_RESPONDER_CONSTANTS.FALLBACK_THREAD_READ_WAIT_MS, null)
2373
+ : Promise.resolve(null);
2374
+ const after = (await readAfter()) ?? (this.deps.slackRepliesAfter ? await readAfter() : null);
2356
2375
  const harnessNotes = [SLACK_TEAM_CHANNEL_CONSTANTS.ROOM_UNANSWERED_NOTE, SLACK_TEAM_CHANNEL_CONSTANTS.ROOM_ROUTE_STALLED_NOTE];
2357
2376
  if (after?.some((m) => m.isBot && !harnessNotes.includes(m.text.trim()))) {
2358
2377
  this.logger.info('Unanswered room message: an agent has replied or is working on it in Slack — no hand-off', {
@@ -2366,19 +2385,26 @@ export class SlackTeamChannelService {
2366
2385
  return;
2367
2386
  }
2368
2387
  const mapping = this.findBySlackChannelId(pending.slackChannelId);
2369
- // Not this machine's to hand over (another may be the watcher), or the
2370
- // thread could not be read just now: tell the owner rather than hand
2371
- // it over blind and risk a second answer.
2372
- const unreadable = !!this.deps.slackRepliesAfter && after === null && !!pending.timer;
2373
- if (pending.noteOnly || unreadable) {
2388
+ // Not this machine's to hand over (another may be the watcher): tell
2389
+ // the owner rather than risk a second answer.
2390
+ if (pending.noteOnly) {
2374
2391
  this.logger.warn('Unanswered room message: telling the owner instead of handing it over', {
2375
2392
  slackChannel: pending.slackChannelId,
2376
2393
  ts: pending.message.ts,
2377
- reason: pending.noteOnly ? 'not this machine\'s hand-off' : 'Slack thread unreadable',
2394
+ reason: 'not this machine\'s hand-off',
2378
2395
  });
2379
2396
  await this.postUnansweredNote(pending, mapping);
2380
2397
  return;
2381
2398
  }
2399
+ // The thread could not be read even now: hand it to the lead anyway.
2400
+ // A possible second answer beats telling the owner "nobody was awake"
2401
+ // while agents here are (2026-10-05, #book-publish: 21 such notes).
2402
+ if (this.deps.slackRepliesAfter && after === null && pending.timer) {
2403
+ this.logger.warn('Unanswered room message: Slack thread unreadable — handing it to the lead anyway', {
2404
+ slackChannel: pending.slackChannelId,
2405
+ ts: pending.message.ts,
2406
+ });
2407
+ }
2382
2408
  const lead = mapping ? await this.localRoomLead(mapping) : null;
2383
2409
  if (mapping && lead && pending.recipients.includes(lead)) {
2384
2410
  // The lead already holds it (delivered as optional): handing it over