crewly 1.20.228 → 1.20.229
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.
- package/config/skills/agent/core/app-comments/SKILL.md +39 -5
- package/config/skills/agent/core/app-comments/execute.sh +9 -4
- package/config/skills/agent/core/app-comments/execute.test.sh +9 -0
- package/dist/backend/backend/src/constants.d.ts +29 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +29 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/services/apps/app-roster.service.d.ts +86 -0
- package/dist/backend/backend/src/services/apps/app-roster.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/apps/app-roster.service.js +119 -0
- package/dist/backend/backend/src/services/apps/app-roster.service.js.map +1 -0
- package/dist/backend/backend/src/services/apps/app-wake-message.d.ts +24 -1
- package/dist/backend/backend/src/services/apps/app-wake-message.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/app-wake-message.js +62 -5
- package/dist/backend/backend/src/services/apps/app-wake-message.js.map +1 -1
- package/dist/backend/backend/src/services/apps/app-wake.service.d.ts +56 -0
- package/dist/backend/backend/src/services/apps/app-wake.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/app-wake.service.js +211 -8
- package/dist/backend/backend/src/services/apps/app-wake.service.js.map +1 -1
- package/dist/backend/backend/src/services/apps/apps-registry.service.d.ts +20 -0
- package/dist/backend/backend/src/services/apps/apps-registry.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/apps-registry.service.js +24 -0
- package/dist/backend/backend/src/services/apps/apps-registry.service.js.map +1 -1
- package/dist/backend/backend/src/services/apps/apps.service.d.ts +42 -0
- package/dist/backend/backend/src/services/apps/apps.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/apps.service.js +52 -5
- package/dist/backend/backend/src/services/apps/apps.service.js.map +1 -1
- package/dist/backend/backend/src/services/apps/apps.wiring.d.ts +7 -0
- package/dist/backend/backend/src/services/apps/apps.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/apps.wiring.js +9 -1
- package/dist/backend/backend/src/services/apps/apps.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +5 -3
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +48 -22
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-thread-context.service.d.ts +1 -1
- package/dist/backend/backend/src/services/slack/slack-thread-context.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-thread-context.service.js +9 -4
- package/dist/backend/backend/src/services/slack/slack-thread-context.service.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +29 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +29 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +5 -3
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +48 -22
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-thread-context.service.d.ts +1 -1
- package/dist/cli/backend/src/services/slack/slack-thread-context.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-thread-context.service.js +9 -4
- package/dist/cli/backend/src/services/slack/slack-thread-context.service.js.map +1 -1
- 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
|
|
1868
|
-
//
|
|
1869
|
-
//
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
|
1936
|
+
return 'trusted';
|
|
1921
1937
|
if (!threadId || !this.deps.chat.listThreadForBridge)
|
|
1922
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
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)
|
|
2370
|
-
//
|
|
2371
|
-
|
|
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:
|
|
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
|