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
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an agent's message queue needs to know about a message beyond its
|
|
3
|
+
* text: whether the owner wrote it (it goes ahead of system traffic), which
|
|
4
|
+
* conversation and thread it belongs to (for a colleague's message or a
|
|
5
|
+
* reminder, a reply there after it was queued means it was answered — never
|
|
6
|
+
* for the owner's own message), the owner message it carries (so a second
|
|
7
|
+
* copy is recognised), and which earlier reminder it replaces.
|
|
8
|
+
*
|
|
9
|
+
* Callers that know this (the chat dispatcher for an owner's message, the
|
|
10
|
+
* decision service for an owner's answer, the owner-message watchdog for its
|
|
11
|
+
* reminder, the open-items service for a "promised work is ready" reminder)
|
|
12
|
+
* run their delivery inside {@link withQueueMeta}. If the agent is busy and
|
|
13
|
+
* the delivery path queues the message, {@link SubAgentMessageQueue.enqueue}
|
|
14
|
+
* picks the meta up from the async context — no delivery signature changes.
|
|
15
|
+
*
|
|
16
|
+
* Case that motivated it (2026-10-05, #education, D-270): the owner's answer
|
|
17
|
+
* to Atlas's decision card was queued at position 7 behind stale reminders,
|
|
18
|
+
* handed over one per idle moment oldest-first, and reached Atlas after
|
|
19
|
+
* ~18 minutes; a duplicate of an owner message Atlas had already answered sat
|
|
20
|
+
* in front of it.
|
|
21
|
+
*
|
|
22
|
+
* @module services/messaging/queue-priority
|
|
23
|
+
*/
|
|
24
|
+
/** Where a queued message's answer belongs. */
|
|
25
|
+
export interface QueueConversation {
|
|
26
|
+
/** chat-v2 channel the message was recorded in */
|
|
27
|
+
chatChannelId?: string;
|
|
28
|
+
/** chat-v2 thread the answer belongs in (huddles) */
|
|
29
|
+
chatThreadId?: string;
|
|
30
|
+
/** Slack channel / DM */
|
|
31
|
+
slackChannelId?: string;
|
|
32
|
+
/** Slack thread root */
|
|
33
|
+
threadTs?: string;
|
|
34
|
+
}
|
|
35
|
+
/** Metadata stored with a queued message. */
|
|
36
|
+
export interface QueueMessageMeta {
|
|
37
|
+
/** The owner wrote it (or it carries the owner's words / answer): goes ahead of everything else */
|
|
38
|
+
owner?: boolean;
|
|
39
|
+
/** Identity of the owner message it carries (`slack:<ch>:<ts>`, `chat:<ch>:<id>`, `decision:<id>`); a later queued copy is a duplicate */
|
|
40
|
+
ref?: string;
|
|
41
|
+
/** Conversation (and thread) the answer belongs in */
|
|
42
|
+
where?: QueueConversation;
|
|
43
|
+
/** A newer queued message with the same key replaces an older one (a reminder about the same promise) */
|
|
44
|
+
supersedeKey?: string;
|
|
45
|
+
/**
|
|
46
|
+
* A harness reminder that carries the owner's words (the watchdog's nudge):
|
|
47
|
+
* owner priority, but — unlike the owner's own message — dropped once the
|
|
48
|
+
* agent has answered in its thread.
|
|
49
|
+
*/
|
|
50
|
+
reminder?: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Run a delivery with queue metadata for one message to one agent. If the
|
|
54
|
+
* delivery queues that message for that agent, the queued copy carries the
|
|
55
|
+
* metadata.
|
|
56
|
+
*
|
|
57
|
+
* @param session - The agent the delivery is for
|
|
58
|
+
* @param data - The exact text being delivered
|
|
59
|
+
* @param meta - The metadata
|
|
60
|
+
* @param fn - The delivery
|
|
61
|
+
* @returns What `fn` returns
|
|
62
|
+
*/
|
|
63
|
+
export declare function withQueueMeta<T>(session: string, data: string, meta: QueueMessageMeta, fn: () => T): T;
|
|
64
|
+
/**
|
|
65
|
+
* The metadata of the delivery running now, if it is this message for this agent.
|
|
66
|
+
*
|
|
67
|
+
* @param session - The agent a message is being queued for
|
|
68
|
+
* @param data - The message being queued
|
|
69
|
+
* @returns The metadata, or undefined
|
|
70
|
+
*/
|
|
71
|
+
export declare function currentQueueMeta(session: string, data: string): QueueMessageMeta | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* The agents' recent posts, by agent. Fed by the chat-v2 turn stream and the
|
|
74
|
+
* `reply` endpoint; read by the queue when it flushes.
|
|
75
|
+
*/
|
|
76
|
+
export declare class AgentPostLog {
|
|
77
|
+
private static instance;
|
|
78
|
+
private readonly posts;
|
|
79
|
+
/** @returns The process-wide log */
|
|
80
|
+
static getInstance(): AgentPostLog;
|
|
81
|
+
/** Reset (tests). */
|
|
82
|
+
static resetInstance(): void;
|
|
83
|
+
/**
|
|
84
|
+
* Record that an agent posted (a real answer, not an interim "working on it").
|
|
85
|
+
*
|
|
86
|
+
* @param session - The agent
|
|
87
|
+
* @param where - Where it posted
|
|
88
|
+
* @param at - When (default now)
|
|
89
|
+
*/
|
|
90
|
+
note(session: string, where: QueueConversation, at?: number): void;
|
|
91
|
+
/**
|
|
92
|
+
* Whether the agent posted in a conversation (and thread) after a moment.
|
|
93
|
+
*
|
|
94
|
+
* Matched by thread only (see {@link sameConversation}).
|
|
95
|
+
*
|
|
96
|
+
* @param session - The agent
|
|
97
|
+
* @param where - The queued message's conversation
|
|
98
|
+
* @param since - When it was queued
|
|
99
|
+
* @returns True when the agent spoke there after `since`
|
|
100
|
+
*/
|
|
101
|
+
postedSince(session: string, where: QueueConversation, since: number): boolean;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Whether a post landed in the conversation (and thread) a queued message belongs to.
|
|
105
|
+
*
|
|
106
|
+
* @param post - Where the agent posted
|
|
107
|
+
* @param queued - Where the queued message's answer belongs
|
|
108
|
+
* @returns True on a match
|
|
109
|
+
*/
|
|
110
|
+
export declare function sameConversation(post: QueueConversation, queued: QueueConversation): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Feed a recorded chat-v2 turn to the post log: an agent's real answer (not
|
|
113
|
+
* an interim "working on it") in a conversation, and the Slack thread it
|
|
114
|
+
* mirrors to when the turn says so.
|
|
115
|
+
*
|
|
116
|
+
* @param dto - The turn (sender, channel, thread, metadata)
|
|
117
|
+
* @param log - The post log (default: the process-wide one)
|
|
118
|
+
*/
|
|
119
|
+
export declare function noteAgentChatTurn(dto: {
|
|
120
|
+
senderType: string;
|
|
121
|
+
senderId: string;
|
|
122
|
+
channelId: string;
|
|
123
|
+
threadId?: string | null;
|
|
124
|
+
createdAt?: number;
|
|
125
|
+
metadata?: Record<string, unknown>;
|
|
126
|
+
}, log?: AgentPostLog): void;
|
|
127
|
+
//# sourceMappingURL=queue-priority.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-priority.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/messaging/queue-priority.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,+CAA+C;AAC/C,MAAM,WAAW,iBAAiB;IACjC,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yBAAyB;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wBAAwB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6CAA6C;AAC7C,MAAM,WAAW,gBAAgB;IAChC,mGAAmG;IACnG,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0IAA0I;IAC1I,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,yGAAyG;IACzG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEtG;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAG5F;AAiBD;;;GAGG;AACH,qBAAa,YAAY;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IAExD,oCAAoC;IACpC,MAAM,CAAC,WAAW,IAAI,YAAY;IAKlC,qBAAqB;IACrB,MAAM,CAAC,aAAa,IAAI,IAAI;IAI5B;;;;;;OAMG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,GAAE,MAAmB,GAAG,IAAI;IAS9E;;;;;;;;;OASG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;CAK9E;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAW5F;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAChC,GAAG,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EAClJ,GAAG,GAAE,YAAyC,GAC5C,IAAI,CAWN"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an agent's message queue needs to know about a message beyond its
|
|
3
|
+
* text: whether the owner wrote it (it goes ahead of system traffic), which
|
|
4
|
+
* conversation and thread it belongs to (for a colleague's message or a
|
|
5
|
+
* reminder, a reply there after it was queued means it was answered — never
|
|
6
|
+
* for the owner's own message), the owner message it carries (so a second
|
|
7
|
+
* copy is recognised), and which earlier reminder it replaces.
|
|
8
|
+
*
|
|
9
|
+
* Callers that know this (the chat dispatcher for an owner's message, the
|
|
10
|
+
* decision service for an owner's answer, the owner-message watchdog for its
|
|
11
|
+
* reminder, the open-items service for a "promised work is ready" reminder)
|
|
12
|
+
* run their delivery inside {@link withQueueMeta}. If the agent is busy and
|
|
13
|
+
* the delivery path queues the message, {@link SubAgentMessageQueue.enqueue}
|
|
14
|
+
* picks the meta up from the async context — no delivery signature changes.
|
|
15
|
+
*
|
|
16
|
+
* Case that motivated it (2026-10-05, #education, D-270): the owner's answer
|
|
17
|
+
* to Atlas's decision card was queued at position 7 behind stale reminders,
|
|
18
|
+
* handed over one per idle moment oldest-first, and reached Atlas after
|
|
19
|
+
* ~18 minutes; a duplicate of an owner message Atlas had already answered sat
|
|
20
|
+
* in front of it.
|
|
21
|
+
*
|
|
22
|
+
* @module services/messaging/queue-priority
|
|
23
|
+
*/
|
|
24
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
25
|
+
const context = new AsyncLocalStorage();
|
|
26
|
+
/**
|
|
27
|
+
* Run a delivery with queue metadata for one message to one agent. If the
|
|
28
|
+
* delivery queues that message for that agent, the queued copy carries the
|
|
29
|
+
* metadata.
|
|
30
|
+
*
|
|
31
|
+
* @param session - The agent the delivery is for
|
|
32
|
+
* @param data - The exact text being delivered
|
|
33
|
+
* @param meta - The metadata
|
|
34
|
+
* @param fn - The delivery
|
|
35
|
+
* @returns What `fn` returns
|
|
36
|
+
*/
|
|
37
|
+
export function withQueueMeta(session, data, meta, fn) {
|
|
38
|
+
return context.run({ session, data, meta }, fn);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The metadata of the delivery running now, if it is this message for this agent.
|
|
42
|
+
*
|
|
43
|
+
* @param session - The agent a message is being queued for
|
|
44
|
+
* @param data - The message being queued
|
|
45
|
+
* @returns The metadata, or undefined
|
|
46
|
+
*/
|
|
47
|
+
export function currentQueueMeta(session, data) {
|
|
48
|
+
const bound = context.getStore();
|
|
49
|
+
return bound && bound.session === session && bound.data === data ? bound.meta : undefined;
|
|
50
|
+
}
|
|
51
|
+
/** How long posts are remembered; a queued message older than this is judged by the other rules. */
|
|
52
|
+
const POST_KEEP_MS = 24 * 60 * 60 * 1000;
|
|
53
|
+
/** Posts remembered per agent. */
|
|
54
|
+
const POSTS_PER_AGENT = 200;
|
|
55
|
+
/**
|
|
56
|
+
* The agents' recent posts, by agent. Fed by the chat-v2 turn stream and the
|
|
57
|
+
* `reply` endpoint; read by the queue when it flushes.
|
|
58
|
+
*/
|
|
59
|
+
export class AgentPostLog {
|
|
60
|
+
static instance = null;
|
|
61
|
+
posts = new Map();
|
|
62
|
+
/** @returns The process-wide log */
|
|
63
|
+
static getInstance() {
|
|
64
|
+
if (!AgentPostLog.instance)
|
|
65
|
+
AgentPostLog.instance = new AgentPostLog();
|
|
66
|
+
return AgentPostLog.instance;
|
|
67
|
+
}
|
|
68
|
+
/** Reset (tests). */
|
|
69
|
+
static resetInstance() {
|
|
70
|
+
AgentPostLog.instance = null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Record that an agent posted (a real answer, not an interim "working on it").
|
|
74
|
+
*
|
|
75
|
+
* @param session - The agent
|
|
76
|
+
* @param where - Where it posted
|
|
77
|
+
* @param at - When (default now)
|
|
78
|
+
*/
|
|
79
|
+
note(session, where, at = Date.now()) {
|
|
80
|
+
if (!session || (!where.chatChannelId && !where.slackChannelId))
|
|
81
|
+
return;
|
|
82
|
+
const list = this.posts.get(session) ?? [];
|
|
83
|
+
list.push({ at, where: { ...where } });
|
|
84
|
+
const cutoff = Date.now() - POST_KEEP_MS;
|
|
85
|
+
const kept = list.filter((p) => p.at >= cutoff).slice(-POSTS_PER_AGENT);
|
|
86
|
+
this.posts.set(session, kept);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Whether the agent posted in a conversation (and thread) after a moment.
|
|
90
|
+
*
|
|
91
|
+
* Matched by thread only (see {@link sameConversation}).
|
|
92
|
+
*
|
|
93
|
+
* @param session - The agent
|
|
94
|
+
* @param where - The queued message's conversation
|
|
95
|
+
* @param since - When it was queued
|
|
96
|
+
* @returns True when the agent spoke there after `since`
|
|
97
|
+
*/
|
|
98
|
+
postedSince(session, where, since) {
|
|
99
|
+
const list = this.posts.get(session);
|
|
100
|
+
if (!list)
|
|
101
|
+
return false;
|
|
102
|
+
return list.some((p) => p.at > since && sameConversation(p.where, where));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Whether a post landed in the conversation (and thread) a queued message belongs to.
|
|
107
|
+
*
|
|
108
|
+
* @param post - Where the agent posted
|
|
109
|
+
* @param queued - Where the queued message's answer belongs
|
|
110
|
+
* @returns True on a match
|
|
111
|
+
*/
|
|
112
|
+
export function sameConversation(post, queued) {
|
|
113
|
+
// Only a thread identifies one conversation. A top-level DM is never
|
|
114
|
+
// matched: the agent posting there about something else (an earlier
|
|
115
|
+
// request finishing) must not make a newer owner message look answered.
|
|
116
|
+
if (queued.chatChannelId && queued.chatThreadId && post.chatChannelId === queued.chatChannelId && post.chatThreadId === queued.chatThreadId) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
if (queued.slackChannelId && queued.threadTs && post.slackChannelId === queued.slackChannelId && post.threadTs === queued.threadTs) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Feed a recorded chat-v2 turn to the post log: an agent's real answer (not
|
|
126
|
+
* an interim "working on it") in a conversation, and the Slack thread it
|
|
127
|
+
* mirrors to when the turn says so.
|
|
128
|
+
*
|
|
129
|
+
* @param dto - The turn (sender, channel, thread, metadata)
|
|
130
|
+
* @param log - The post log (default: the process-wide one)
|
|
131
|
+
*/
|
|
132
|
+
export function noteAgentChatTurn(dto, log = AgentPostLog.getInstance()) {
|
|
133
|
+
if (dto.senderType !== 'agent' || !dto.senderId)
|
|
134
|
+
return;
|
|
135
|
+
const meta = dto.metadata ?? {};
|
|
136
|
+
if (meta.interim === true)
|
|
137
|
+
return;
|
|
138
|
+
const slackChannelId = typeof meta.slackChannelId === 'string' ? meta.slackChannelId : undefined;
|
|
139
|
+
const threadTs = typeof meta.slackThreadTs === 'string' ? meta.slackThreadTs : undefined;
|
|
140
|
+
log.note(dto.senderId, {
|
|
141
|
+
chatChannelId: dto.channelId,
|
|
142
|
+
...(dto.threadId ? { chatThreadId: dto.threadId } : {}),
|
|
143
|
+
...(slackChannelId && threadTs ? { slackChannelId, threadTs } : {}),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=queue-priority.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-priority.js","sourceRoot":"","sources":["../../../../../../backend/src/services/messaging/queue-priority.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module sub-agent-message-queue
|
|
12
12
|
*/
|
|
13
|
+
import { type QueueMessageMeta } from './queue-priority.js';
|
|
13
14
|
/**
|
|
14
15
|
* A single queued message destined for a sub-agent.
|
|
15
16
|
*/
|
|
@@ -38,7 +39,23 @@ export interface QueuedAgentMessage {
|
|
|
38
39
|
* conversation — runs when it is finally delivered (crewly#1015 review).
|
|
39
40
|
*/
|
|
40
41
|
workItemId?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Who wrote it and where its answer belongs (see queue-priority.ts):
|
|
44
|
+
* owner messages go ahead of system traffic, and a message answered in
|
|
45
|
+
* its thread, a second copy of an owner message, or a reminder replaced
|
|
46
|
+
* by a newer one is dropped at flush time.
|
|
47
|
+
*/
|
|
48
|
+
meta?: QueueMessageMeta;
|
|
41
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Owner messages first, each group in its existing order (stable).
|
|
52
|
+
*
|
|
53
|
+
* @param list - Queue
|
|
54
|
+
* @returns The queue in delivery order
|
|
55
|
+
*/
|
|
56
|
+
export declare function ownerFirst(list: QueuedAgentMessage[]): QueuedAgentMessage[];
|
|
57
|
+
/** Why a queued message was dropped before delivery as no longer needed. */
|
|
58
|
+
export type QueuePruneReason = 'answered-in-thread' | 'duplicate-owner-message' | 'superseded';
|
|
42
59
|
/** A hand-over prepared for a queued WorkItem brief (see {@link SubAgentMessageQueue.setHandOverPreparer}). */
|
|
43
60
|
export interface QueuedHandOver {
|
|
44
61
|
/** Text to write */
|
|
@@ -198,19 +215,26 @@ export declare class SubAgentMessageQueue {
|
|
|
198
215
|
}>;
|
|
199
216
|
/**
|
|
200
217
|
* Enqueue a message for a session that is not yet active.
|
|
201
|
-
* If the queue exceeds MAX_QUEUE_SIZE, the oldest message is
|
|
218
|
+
* If the queue exceeds MAX_QUEUE_SIZE, the oldest non-owner message is
|
|
219
|
+
* dropped (the oldest message when all are the owner's).
|
|
202
220
|
*
|
|
203
221
|
* A message identical to one already waiting for the same session is not
|
|
204
222
|
* added again: the reconciler re-sends the same reminder while an agent
|
|
205
223
|
* is stopped (daily token cap, busy), and each copy would cost the agent
|
|
206
|
-
* a turn once delivered. The waiting copy keeps its place.
|
|
224
|
+
* a turn once delivered. The waiting copy keeps its place. Neither is a
|
|
225
|
+
* second copy of the same owner message (same `ref`).
|
|
226
|
+
*
|
|
227
|
+
* Owner messages (see {@link QueueMessageMeta}, taken from `meta.queueMeta`
|
|
228
|
+
* or the delivery running now — {@link withQueueMeta}) go to the front,
|
|
229
|
+
* behind owner messages already waiting; everything else goes to the back.
|
|
207
230
|
*
|
|
208
231
|
* @param sessionName - The target agent session name
|
|
209
232
|
* @param data - The raw data string to deliver later
|
|
210
|
-
* @param meta - `workItemId` when the message hands over a WorkItem
|
|
233
|
+
* @param meta - `workItemId` when the message hands over a WorkItem; `queueMeta` to override the context
|
|
211
234
|
*/
|
|
212
235
|
enqueue(sessionName: string, data: string, meta?: {
|
|
213
236
|
workItemId?: string;
|
|
237
|
+
queueMeta?: QueueMessageMeta;
|
|
214
238
|
}): void;
|
|
215
239
|
/**
|
|
216
240
|
* Dequeue all pending messages for a session (FIFO order).
|
|
@@ -257,6 +281,34 @@ export declare class SubAgentMessageQueue {
|
|
|
257
281
|
failed: number;
|
|
258
282
|
skippedStale: number;
|
|
259
283
|
}>;
|
|
284
|
+
/**
|
|
285
|
+
* Drop queued messages that are no longer needed, before delivery:
|
|
286
|
+
* a later copy of an owner message already queued (same `ref`), and a
|
|
287
|
+
* reminder replaced by a newer one (same `supersedeKey`; the newest is
|
|
288
|
+
* kept). Each drop is logged. Messages without metadata are untouched.
|
|
289
|
+
*
|
|
290
|
+
* @param sessionName - The agent
|
|
291
|
+
* @param pending - Its queue, in delivery order
|
|
292
|
+
* @param out - Counters (`skippedStale` is incremented per drop)
|
|
293
|
+
* @returns The messages still to deliver, in order
|
|
294
|
+
*/
|
|
295
|
+
private pruneNoLongerNeeded;
|
|
296
|
+
/**
|
|
297
|
+
* Whether the agent has already posted in the thread a queued message
|
|
298
|
+
* belongs to, after it was queued — it was answered (the agent read the
|
|
299
|
+
* thread). Only messages that name a thread are judged this way.
|
|
300
|
+
*
|
|
301
|
+
* Never an owner's own message (or the owner's decision answer): the owner
|
|
302
|
+
* may ask a follow-up in the same thread while the agent is still
|
|
303
|
+
* answering the first question, and that post must not make the follow-up
|
|
304
|
+
* look answered. Owner messages are dropped only as exact duplicates.
|
|
305
|
+
* Harness reminders carrying the owner's words (`reminder`) are judged.
|
|
306
|
+
*
|
|
307
|
+
* @param m - Queued message
|
|
308
|
+
* @returns True when it was answered
|
|
309
|
+
*/
|
|
310
|
+
private answeredInThread;
|
|
311
|
+
private logPrune;
|
|
260
312
|
/**
|
|
261
313
|
* Sessions that still have messages waiting for them. A restart must bring
|
|
262
314
|
* these agents back: a message queued for an agent is work in hand even
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sub-agent-message-queue.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/messaging/sub-agent-message-queue.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"sub-agent-message-queue.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/messaging/sub-agent-message-queue.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,6 +16,28 @@ import { SUB_AGENT_QUEUE_CONSTANTS } from '../../constants.js';
|
|
|
16
16
|
import { mkdirSync, existsSync } from 'fs';
|
|
17
17
|
import { atomicWriteFileSync, quarantineCorruptFileSync, readJsonStoreSync, CorruptJsonFileError } from '../../utils/file-io.utils.js';
|
|
18
18
|
import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
|
|
19
|
+
import { AgentPostLog, currentQueueMeta } from './queue-priority.js';
|
|
20
|
+
/**
|
|
21
|
+
* Whether a queued message was written by (or carries the answer of) the owner.
|
|
22
|
+
*
|
|
23
|
+
* @param m - Queued message
|
|
24
|
+
* @returns True for owner traffic
|
|
25
|
+
*/
|
|
26
|
+
function isOwnerItem(m) {
|
|
27
|
+
return m.meta?.owner === true;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Owner messages first, each group in its existing order (stable).
|
|
31
|
+
*
|
|
32
|
+
* @param list - Queue
|
|
33
|
+
* @returns The queue in delivery order
|
|
34
|
+
*/
|
|
35
|
+
export function ownerFirst(list) {
|
|
36
|
+
const owner = list.filter(isOwnerItem);
|
|
37
|
+
if (owner.length === 0 || owner.length === list.length)
|
|
38
|
+
return list;
|
|
39
|
+
return [...owner, ...list.filter((m) => !isOwnerItem(m))];
|
|
40
|
+
}
|
|
19
41
|
/**
|
|
20
42
|
* Singleton service that holds pending messages per agent session.
|
|
21
43
|
*
|
|
@@ -103,7 +125,7 @@ export class SubAgentMessageQueue {
|
|
|
103
125
|
if (usable.length === 0)
|
|
104
126
|
continue;
|
|
105
127
|
// Restored messages are older than anything queued since: they go first.
|
|
106
|
-
this.pendingMessages.set(sessionName, [...usable, ...current]);
|
|
128
|
+
this.pendingMessages.set(sessionName, ownerFirst([...usable, ...current]));
|
|
107
129
|
restored += usable.length;
|
|
108
130
|
}
|
|
109
131
|
if (restored > 0) {
|
|
@@ -338,18 +360,25 @@ export class SubAgentMessageQueue {
|
|
|
338
360
|
}
|
|
339
361
|
/**
|
|
340
362
|
* Enqueue a message for a session that is not yet active.
|
|
341
|
-
* If the queue exceeds MAX_QUEUE_SIZE, the oldest message is
|
|
363
|
+
* If the queue exceeds MAX_QUEUE_SIZE, the oldest non-owner message is
|
|
364
|
+
* dropped (the oldest message when all are the owner's).
|
|
342
365
|
*
|
|
343
366
|
* A message identical to one already waiting for the same session is not
|
|
344
367
|
* added again: the reconciler re-sends the same reminder while an agent
|
|
345
368
|
* is stopped (daily token cap, busy), and each copy would cost the agent
|
|
346
|
-
* a turn once delivered. The waiting copy keeps its place.
|
|
369
|
+
* a turn once delivered. The waiting copy keeps its place. Neither is a
|
|
370
|
+
* second copy of the same owner message (same `ref`).
|
|
371
|
+
*
|
|
372
|
+
* Owner messages (see {@link QueueMessageMeta}, taken from `meta.queueMeta`
|
|
373
|
+
* or the delivery running now — {@link withQueueMeta}) go to the front,
|
|
374
|
+
* behind owner messages already waiting; everything else goes to the back.
|
|
347
375
|
*
|
|
348
376
|
* @param sessionName - The target agent session name
|
|
349
377
|
* @param data - The raw data string to deliver later
|
|
350
|
-
* @param meta - `workItemId` when the message hands over a WorkItem
|
|
378
|
+
* @param meta - `workItemId` when the message hands over a WorkItem; `queueMeta` to override the context
|
|
351
379
|
*/
|
|
352
380
|
enqueue(sessionName, data, meta = {}) {
|
|
381
|
+
const queueMeta = meta.queueMeta ?? currentQueueMeta(sessionName, data);
|
|
353
382
|
let queue = this.pendingMessages.get(sessionName);
|
|
354
383
|
if (!queue) {
|
|
355
384
|
queue = [];
|
|
@@ -363,9 +392,18 @@ export class SubAgentMessageQueue {
|
|
|
363
392
|
});
|
|
364
393
|
return;
|
|
365
394
|
}
|
|
366
|
-
|
|
395
|
+
if (queueMeta?.ref && queue.some((m) => m.meta?.ref === queueMeta.ref)) {
|
|
396
|
+
this.logger.info('Same owner message already queued for the agent — not added again', {
|
|
397
|
+
sessionName,
|
|
398
|
+
ref: queueMeta.ref,
|
|
399
|
+
queueSize: queue.length,
|
|
400
|
+
});
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
// Drop the oldest non-owner message if at capacity (the owner's last).
|
|
367
404
|
if (queue.length >= SUB_AGENT_QUEUE_CONSTANTS.MAX_QUEUE_SIZE) {
|
|
368
|
-
const
|
|
405
|
+
const victim = queue.findIndex((m) => !isOwnerItem(m));
|
|
406
|
+
const dropped = queue.splice(victim >= 0 ? victim : 0, 1)[0];
|
|
369
407
|
this.logger.warn('Queue at capacity, dropping oldest message', {
|
|
370
408
|
sessionName,
|
|
371
409
|
droppedAt: dropped?.queuedAt,
|
|
@@ -374,16 +412,27 @@ export class SubAgentMessageQueue {
|
|
|
374
412
|
if (dropped)
|
|
375
413
|
this.reportDrop(sessionName, [dropped], 'capacity');
|
|
376
414
|
}
|
|
377
|
-
|
|
415
|
+
const item = {
|
|
378
416
|
data,
|
|
379
417
|
queuedAt: Date.now(),
|
|
380
418
|
sessionName,
|
|
381
419
|
...(meta.workItemId ? { workItemId: meta.workItemId } : {}),
|
|
382
|
-
|
|
420
|
+
...(queueMeta && Object.keys(queueMeta).length > 0 ? { meta: { ...queueMeta } } : {}),
|
|
421
|
+
};
|
|
422
|
+
// The owner's messages go ahead of system traffic, in the order they came.
|
|
423
|
+
let position = queue.length;
|
|
424
|
+
if (isOwnerItem(item)) {
|
|
425
|
+
const firstOther = queue.findIndex((m) => !isOwnerItem(m));
|
|
426
|
+
if (firstOther >= 0)
|
|
427
|
+
position = firstOther;
|
|
428
|
+
}
|
|
429
|
+
queue.splice(position, 0, item);
|
|
383
430
|
this.save();
|
|
384
431
|
this.logger.info('Message queued for sub-agent', {
|
|
385
432
|
sessionName,
|
|
386
433
|
queueSize: queue.length,
|
|
434
|
+
position: position + 1,
|
|
435
|
+
...(isOwnerItem(item) ? { owner: true } : {}),
|
|
387
436
|
dataLength: data.length,
|
|
388
437
|
});
|
|
389
438
|
}
|
|
@@ -446,8 +495,8 @@ export class SubAgentMessageQueue {
|
|
|
446
495
|
* @returns How many were delivered, deferred again, failed, and dropped as stale
|
|
447
496
|
*/
|
|
448
497
|
async flush(sessionName, send, gapMs = 0) {
|
|
449
|
-
const pending = this.dequeueAll(sessionName);
|
|
450
498
|
const out = { delivered: 0, deferred: 0, failed: 0, skippedStale: 0 };
|
|
499
|
+
const pending = this.pruneNoLongerNeeded(sessionName, this.dequeueAll(sessionName), out);
|
|
451
500
|
// A send that failed ("Session does not exist", "Runtime has exited")
|
|
452
501
|
// or threw is not a delivery: the message goes back on the queue, up
|
|
453
502
|
// to MAX_DELIVERY_ATTEMPTS, then the drop is reported (crewly#1014).
|
|
@@ -473,6 +522,11 @@ export class SubAgentMessageQueue {
|
|
|
473
522
|
}
|
|
474
523
|
};
|
|
475
524
|
for (const [i, queued] of pending.entries()) {
|
|
525
|
+
if (this.answeredInThread(queued)) {
|
|
526
|
+
out.skippedStale += 1;
|
|
527
|
+
this.logPrune(sessionName, queued, 'answered-in-thread');
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
476
530
|
if (await this.isStale(queued.data, sessionName)) {
|
|
477
531
|
out.skippedStale += 1;
|
|
478
532
|
continue;
|
|
@@ -541,7 +595,7 @@ export class SubAgentMessageQueue {
|
|
|
541
595
|
// Back at the front, in their original order, ahead of anything
|
|
542
596
|
// queued meanwhile (failed ones came before the held one).
|
|
543
597
|
const queue = (this.pendingMessages.get(sessionName) ?? []).filter((m) => !heldAgain.some((h) => h.data === m.data) && !failedAgain.some((f) => f.data === m.data));
|
|
544
|
-
this.pendingMessages.set(sessionName, [...failedAgain, ...heldAgain, ...queue]);
|
|
598
|
+
this.pendingMessages.set(sessionName, ownerFirst([...failedAgain, ...heldAgain, ...queue]));
|
|
545
599
|
this.save();
|
|
546
600
|
}
|
|
547
601
|
if (undeliverable.length > 0)
|
|
@@ -555,6 +609,84 @@ export class SubAgentMessageQueue {
|
|
|
555
609
|
}
|
|
556
610
|
return out;
|
|
557
611
|
}
|
|
612
|
+
/**
|
|
613
|
+
* Drop queued messages that are no longer needed, before delivery:
|
|
614
|
+
* a later copy of an owner message already queued (same `ref`), and a
|
|
615
|
+
* reminder replaced by a newer one (same `supersedeKey`; the newest is
|
|
616
|
+
* kept). Each drop is logged. Messages without metadata are untouched.
|
|
617
|
+
*
|
|
618
|
+
* @param sessionName - The agent
|
|
619
|
+
* @param pending - Its queue, in delivery order
|
|
620
|
+
* @param out - Counters (`skippedStale` is incremented per drop)
|
|
621
|
+
* @returns The messages still to deliver, in order
|
|
622
|
+
*/
|
|
623
|
+
pruneNoLongerNeeded(sessionName, pending, out) {
|
|
624
|
+
const newestBySupersede = new Map();
|
|
625
|
+
for (const m of pending) {
|
|
626
|
+
const key = m.meta?.supersedeKey;
|
|
627
|
+
if (!key)
|
|
628
|
+
continue;
|
|
629
|
+
const cur = newestBySupersede.get(key);
|
|
630
|
+
if (!cur || m.queuedAt >= cur.queuedAt)
|
|
631
|
+
newestBySupersede.set(key, m);
|
|
632
|
+
}
|
|
633
|
+
const seenRefs = new Set();
|
|
634
|
+
const kept = [];
|
|
635
|
+
for (const m of pending) {
|
|
636
|
+
const key = m.meta?.supersedeKey;
|
|
637
|
+
if (key && newestBySupersede.get(key) !== m) {
|
|
638
|
+
out.skippedStale += 1;
|
|
639
|
+
this.logPrune(sessionName, m, 'superseded');
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const ref = m.meta?.ref;
|
|
643
|
+
if (ref) {
|
|
644
|
+
if (seenRefs.has(ref)) {
|
|
645
|
+
out.skippedStale += 1;
|
|
646
|
+
this.logPrune(sessionName, m, 'duplicate-owner-message');
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
seenRefs.add(ref);
|
|
650
|
+
}
|
|
651
|
+
kept.push(m);
|
|
652
|
+
}
|
|
653
|
+
if (kept.length < pending.length)
|
|
654
|
+
this.save();
|
|
655
|
+
return kept;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Whether the agent has already posted in the thread a queued message
|
|
659
|
+
* belongs to, after it was queued — it was answered (the agent read the
|
|
660
|
+
* thread). Only messages that name a thread are judged this way.
|
|
661
|
+
*
|
|
662
|
+
* Never an owner's own message (or the owner's decision answer): the owner
|
|
663
|
+
* may ask a follow-up in the same thread while the agent is still
|
|
664
|
+
* answering the first question, and that post must not make the follow-up
|
|
665
|
+
* look answered. Owner messages are dropped only as exact duplicates.
|
|
666
|
+
* Harness reminders carrying the owner's words (`reminder`) are judged.
|
|
667
|
+
*
|
|
668
|
+
* @param m - Queued message
|
|
669
|
+
* @returns True when it was answered
|
|
670
|
+
*/
|
|
671
|
+
answeredInThread(m) {
|
|
672
|
+
const where = m.meta?.where;
|
|
673
|
+
if (!where)
|
|
674
|
+
return false;
|
|
675
|
+
if (m.meta?.owner && !m.meta.reminder)
|
|
676
|
+
return false;
|
|
677
|
+
return AgentPostLog.getInstance().postedSince(m.sessionName, where, m.queuedAt);
|
|
678
|
+
}
|
|
679
|
+
logPrune(sessionName, m, reason) {
|
|
680
|
+
this.logger.info('Queued message dropped before delivery — no longer needed', {
|
|
681
|
+
sessionName,
|
|
682
|
+
reason,
|
|
683
|
+
queuedAt: new Date(m.queuedAt).toISOString(),
|
|
684
|
+
...(m.meta?.owner ? { owner: true } : {}),
|
|
685
|
+
...(m.meta?.ref ? { ref: m.meta.ref } : {}),
|
|
686
|
+
...(m.meta?.supersedeKey ? { supersedeKey: m.meta.supersedeKey } : {}),
|
|
687
|
+
preview: m.data.slice(0, 80),
|
|
688
|
+
});
|
|
689
|
+
}
|
|
558
690
|
/**
|
|
559
691
|
* Sessions that still have messages waiting for them. A restart must bring
|
|
560
692
|
* these agents back: a message queued for an agent is work in hand even
|