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.
- package/dist/backend/backend/src/constants.d.ts +11 -1
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +11 -1
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/chat/agent-reply.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat/agent-reply.controller.js +6 -0
- package/dist/backend/backend/src/controllers/chat/agent-reply.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +10 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/agent/claude-runtime.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/claude-runtime.service.js +15 -0
- package/dist/backend/backend/src/services/agent/claude-runtime.service.js.map +1 -1
- package/dist/backend/backend/src/services/apps/app-thumbnail.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/app-thumbnail.service.js +8 -2
- package/dist/backend/backend/src/services/apps/app-thumbnail.service.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +35 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +64 -4
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision.service.d.ts +6 -0
- package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision.service.js +29 -9
- package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.d.ts +9 -1
- package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.js +8 -2
- package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/owner-message-watchdog.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/owner-message-watchdog.wiring.js +20 -2
- package/dist/backend/backend/src/services/messaging/owner-message-watchdog.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/queue-priority.d.ts +127 -0
- package/dist/backend/backend/src/services/messaging/queue-priority.d.ts.map +1 -0
- package/dist/backend/backend/src/services/messaging/queue-priority.js +146 -0
- package/dist/backend/backend/src/services/messaging/queue-priority.js.map +1 -0
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +55 -3
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js +142 -10
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-items.service.d.ts +12 -0
- package/dist/backend/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/open-items/open-items.service.js +17 -2
- package/dist/backend/backend/src/services/open-items/open-items.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js +25 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +36 -0
- 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 +134 -5
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +35 -4
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +95 -0
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +11 -1
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +11 -1
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/claude-runtime.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/claude-runtime.service.js +15 -0
- package/dist/cli/backend/src/services/agent/claude-runtime.service.js.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +35 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +64 -4
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision.service.d.ts +6 -0
- package/dist/cli/backend/src/services/decisions/decision.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision.service.js +29 -9
- package/dist/cli/backend/src/services/decisions/decision.service.js.map +1 -1
- package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.d.ts +9 -1
- package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.js +8 -2
- package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.js.map +1 -1
- package/dist/cli/backend/src/services/messaging/queue-priority.d.ts +127 -0
- package/dist/cli/backend/src/services/messaging/queue-priority.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/queue-priority.js +146 -0
- package/dist/cli/backend/src/services/messaging/queue-priority.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +55 -3
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js +142 -10
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
- package/dist/cli/backend/src/services/open-items/open-items.service.d.ts +12 -0
- package/dist/cli/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/open-items/open-items.service.js +17 -2
- package/dist/cli/backend/src/services/open-items/open-items.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +36 -0
- 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 +134 -5
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts +35 -4
- package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.js +95 -0
- package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
- 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
|
-
|
|
1776
|
-
|
|
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
|
-
},
|
|
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
|