crewly 1.20.365 → 1.20.366
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/attach-file/SKILL.md +7 -0
- package/config/skills/agent/core/attach-file/execute.sh +8 -3
- package/config/skills/agent/core/complete-task/SKILL.md +4 -2
- package/config/skills/agent/core/complete-task/execute.sh +9 -1
- package/config/skills/agent/core/complete-task/explicit-id.test.sh +13 -2
- package/config/skills/agent/core/create-task/SKILL.md +2 -0
- package/config/skills/agent/core/create-task/execute.sh +27 -1
- package/config/skills/agent/core/create-task/execute.test.sh +67 -0
- package/config/skills/agent/core/handoff-task/execute.sh +12 -5
- package/config/skills/agent/core/handoff-task/explicit-id.test.sh +70 -0
- package/config/skills/agent/core/reply-channel/SKILL.md +12 -5
- package/config/skills/agent/core/reply-channel/execute.sh +17 -2
- package/config/skills/agent/core/reply-channel/execute.test.sh +15 -2
- package/config/skills/agent/core/slack-post/SKILL.md +3 -3
- package/config/skills/orchestrator/delegate-task/SKILL.md +1 -1
- package/config/skills/orchestrator/reply-slack/execute.sh +6 -2
- package/config/skills/orchestrator/send-pdf-to-slack/SKILL.md +2 -1
- package/config/skills/orchestrator/send-pdf-to-slack/execute.sh +10 -2
- package/config/skills/registry.json +6 -6
- package/config/skills/team-leader/delegate-task/SKILL.md +1 -1
- package/config/skills/team-leader/delegate-task/execute.sh +6 -0
- package/dist/backend/backend/src/constants.d.ts +26 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +26 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/agent-hooks/agent-hooks.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/agent-hooks/agent-hooks.controller.js +7 -3
- package/dist/backend/backend/src/controllers/agent-hooks/agent-hooks.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts +12 -0
- package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.js +56 -7
- package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts +9 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js +111 -11
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js +52 -4
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +58 -4
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +36 -2
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +16 -10
- 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 +19 -11
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/agent-message-envelope.d.ts +8 -0
- package/dist/backend/backend/src/services/messaging/agent-message-envelope.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/agent-message-envelope.js +10 -1
- package/dist/backend/backend/src/services/messaging/agent-message-envelope.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/owner-hook-message.d.ts +62 -20
- package/dist/backend/backend/src/services/messaging/owner-hook-message.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/owner-hook-message.js +178 -39
- package/dist/backend/backend/src/services/messaging/owner-hook-message.js.map +1 -1
- package/dist/backend/backend/src/services/orc/agent-mention-post-guard.d.ts +8 -5
- package/dist/backend/backend/src/services/orc/agent-mention-post-guard.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/agent-mention-post-guard.js +13 -10
- package/dist/backend/backend/src/services/orc/agent-mention-post-guard.js.map +1 -1
- package/dist/backend/backend/src/services/orc/message-route.d.ts +129 -0
- package/dist/backend/backend/src/services/orc/message-route.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orc/message-route.js +197 -0
- package/dist/backend/backend/src/services/orc/message-route.js.map +1 -0
- package/dist/backend/backend/src/services/orc/reply-destination-resolver.d.ts +49 -6
- package/dist/backend/backend/src/services/orc/reply-destination-resolver.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/reply-destination-resolver.js +284 -69
- package/dist/backend/backend/src/services/orc/reply-destination-resolver.js.map +1 -1
- package/dist/backend/backend/src/services/orc/reply-destination.wiring.d.ts +64 -8
- package/dist/backend/backend/src/services/orc/reply-destination.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/reply-destination.wiring.js +215 -100
- package/dist/backend/backend/src/services/orc/reply-destination.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/orc/triggered-by.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/triggered-by.js +9 -0
- package/dist/backend/backend/src/services/orc/triggered-by.js.map +1 -1
- package/dist/backend/backend/src/services/orc/work-item-destination.d.ts +2 -1
- package/dist/backend/backend/src/services/orc/work-item-destination.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/work-item-destination.js +1 -1
- package/dist/backend/backend/src/services/orc/work-item-destination.js.map +1 -1
- package/dist/backend/backend/src/services/orc/work-item-destination.wiring.d.ts +10 -1
- package/dist/backend/backend/src/services/orc/work-item-destination.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/work-item-destination.wiring.js +25 -4
- package/dist/backend/backend/src/services/orc/work-item-destination.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.d.ts +1 -0
- package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-agent-post.service.d.ts +1 -1
- package/dist/backend/backend/src/services/slack/slack-agent-post.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-agent-post.service.js +20 -13
- package/dist/backend/backend/src/services/slack/slack-agent-post.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +20 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +58 -3
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +31 -0
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js +62 -5
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/backend/backend/src/services/trace/turn-trigger.d.ts +34 -1
- package/dist/backend/backend/src/services/trace/turn-trigger.d.ts.map +1 -1
- package/dist/backend/backend/src/services/trace/turn-trigger.js +74 -9
- package/dist/backend/backend/src/services/trace/turn-trigger.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +26 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +26 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +16 -10
- 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 +19 -11
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/cli/backend/src/services/messaging/agent-message-envelope.d.ts +8 -0
- package/dist/cli/backend/src/services/messaging/agent-message-envelope.d.ts.map +1 -1
- package/dist/cli/backend/src/services/messaging/agent-message-envelope.js +10 -1
- package/dist/cli/backend/src/services/messaging/agent-message-envelope.js.map +1 -1
- package/dist/cli/backend/src/services/orc/message-route.d.ts +129 -0
- package/dist/cli/backend/src/services/orc/message-route.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orc/message-route.js +197 -0
- package/dist/cli/backend/src/services/orc/message-route.js.map +1 -0
- package/dist/cli/backend/src/services/orc/work-item-destination.d.ts +2 -1
- package/dist/cli/backend/src/services/orc/work-item-destination.d.ts.map +1 -1
- package/dist/cli/backend/src/services/orc/work-item-destination.js +1 -1
- package/dist/cli/backend/src/services/orc/work-item-destination.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts +20 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.js +58 -3
- package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +31 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js +62 -5
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/cli/backend/src/services/trace/turn-trigger.d.ts +34 -1
- package/dist/cli/backend/src/services/trace/turn-trigger.d.ts.map +1 -1
- package/dist/cli/backend/src/services/trace/turn-trigger.js +74 -9
- package/dist/cli/backend/src/services/trace/turn-trigger.js.map +1 -1
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { type ReplyDestination, type ReplyHints, type ReplyReference, type ReplyResolverDeps } from './reply-destination-resolver.js';
|
|
10
10
|
import { type WorkDestinationDeps } from './work-item-destination.wiring.js';
|
|
11
|
+
import { type MessageRoute, type RouteCheckOutcome } from './message-route.js';
|
|
11
12
|
import { type TurnTrigger } from '../trace/turn-trigger.js';
|
|
12
13
|
/** Forget recent deliveries (tests). */
|
|
13
14
|
export declare function resetReplyDeliveryGuardForTesting(): void;
|
|
@@ -51,6 +52,18 @@ export interface ReplyDeliveryDeps {
|
|
|
51
52
|
turnTrigger?: (session: string) => TurnTrigger | null;
|
|
52
53
|
/** The owner wrote to the agent in this conversation recently (its post there is an answer). Omitted: no. */
|
|
53
54
|
ownerAskedRecently?: (session: string, conversationId: string) => boolean;
|
|
55
|
+
/**
|
|
56
|
+
* The owner wrote to the agent in this conversation earlier in its working
|
|
57
|
+
* window: a post there answers that, not the agent's newest owner message
|
|
58
|
+
* elsewhere (R8: Pia's #flopost answer said "Triggered by your message in
|
|
59
|
+
* #awesome-videos"). Omitted: no.
|
|
60
|
+
*/
|
|
61
|
+
ownerAskedEarlier?: (session: string, conversationId: string) => boolean;
|
|
62
|
+
/**
|
|
63
|
+
* The latest trigger that names what the agent answers (turn-trigger.ts
|
|
64
|
+
* `routedTrigger`), for the route check. Omitted: no check.
|
|
65
|
+
*/
|
|
66
|
+
routedTrigger?: (session: string) => TurnTrigger | null;
|
|
54
67
|
/** The session's current run trace id. Omitted: none. */
|
|
55
68
|
currentTrace?: (session: string) => string | null;
|
|
56
69
|
/** A conversation as the owner knows it (`#content-team`, `the DM with Ella`). Omitted: unnamed. */
|
|
@@ -96,19 +109,41 @@ export interface DeliverReplyInput {
|
|
|
96
109
|
* Resolve and deliver an agent's message. Never guesses: a destination that
|
|
97
110
|
* does not take the message is reported as an error with the command to run.
|
|
98
111
|
*
|
|
99
|
-
* When the chosen place refuses the message, only
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* - the current work's Slack place → the owner's turn origin, else the DM.
|
|
112
|
+
* When the chosen place refuses the message, only the SAME place is tried
|
|
113
|
+
* by another way (each logged): the Slack thread a delegate was handed, or
|
|
114
|
+
* the Slack thread a chat thread with no row stands for. It is never moved
|
|
115
|
+
* to another place — not a new top-level post, not the owner DM, not the
|
|
116
|
+
* agent's last owner conversation (message-routing audit 2026-10-18, R2):
|
|
117
|
+
* the agent gets the error and the command that works.
|
|
106
118
|
*
|
|
107
119
|
* @param input - Agent, text, references, hints
|
|
108
120
|
* @param deps - Collaborators (default: the real ones)
|
|
109
121
|
* @returns Where it landed, or an English error
|
|
110
122
|
*/
|
|
111
123
|
export declare function deliverReply(input: DeliverReplyInput, deps?: ReplyDeliveryDeps): Promise<ReplyDelivery>;
|
|
124
|
+
/**
|
|
125
|
+
* Counts of the route check since boot.
|
|
126
|
+
*
|
|
127
|
+
* @returns match / mismatch / unknown counts
|
|
128
|
+
*/
|
|
129
|
+
export declare function getRouteCheckStats(): Readonly<Record<RouteCheckOutcome, number>>;
|
|
130
|
+
/** Zero the route check counts (tests). */
|
|
131
|
+
export declare function resetRouteCheckStatsForTesting(): void;
|
|
132
|
+
/**
|
|
133
|
+
* Compare where a delivered post landed with the place of the request it
|
|
134
|
+
* answers (the route of the agent's latest routed input), and log a
|
|
135
|
+
* mismatch. A post the agent addressed by an explicit reference answers that
|
|
136
|
+
* reference and is not checked; an internal message to an agent has no
|
|
137
|
+
* Slack place. Never throws, never blocks: it is a regression signal.
|
|
138
|
+
*
|
|
139
|
+
* @param input - The delivered input
|
|
140
|
+
* @param delivery - Where it landed
|
|
141
|
+
* @param deps - Collaborators (default: the real ones)
|
|
142
|
+
* @returns The outcome, or null when not checked
|
|
143
|
+
*/
|
|
144
|
+
export declare function checkLandedRoute(input: DeliverReplyInput, delivery: Extract<ReplyDelivery, {
|
|
145
|
+
ok: true;
|
|
146
|
+
}>, deps?: ReplyDeliveryDeps): Promise<RouteCheckOutcome | null>;
|
|
112
147
|
/**
|
|
113
148
|
* Provenance for a post an agent makes straight into a conversation
|
|
114
149
|
* (`reply-channel`, `POST /chat/channels/:id/messages`), outside
|
|
@@ -124,10 +159,23 @@ export declare function provenanceForAgentPost(input: {
|
|
|
124
159
|
conversationId: string;
|
|
125
160
|
content: string;
|
|
126
161
|
interim?: boolean;
|
|
127
|
-
}, deps?: Pick<ReplyDeliveryDeps, 'resolver' | 'turnTrigger' | 'ownerAskedRecently' | 'currentTrace' | 'conversationPlace'>): Promise<{
|
|
162
|
+
}, deps?: Pick<ReplyDeliveryDeps, 'resolver' | 'turnTrigger' | 'ownerAskedRecently' | 'ownerAskedEarlier' | 'currentTrace' | 'conversationPlace'>): Promise<{
|
|
128
163
|
content: string;
|
|
129
164
|
triggeredBy?: Record<string, unknown>;
|
|
130
165
|
}>;
|
|
166
|
+
/**
|
|
167
|
+
* Whether a chat thread is agent-to-agent coordination kept inside Crewly
|
|
168
|
+
* (crewly#1340): the message, or the root of its thread, carries
|
|
169
|
+
* `agentCoordinationInternal`. `reply-channel` keeps a post in such a thread
|
|
170
|
+
* internal; a file must not go to the Slack thread key stored on that
|
|
171
|
+
* message either (Pia's v3/v4 videos for Ella landed in Steve's thread,
|
|
172
|
+
* 2026-10-18).
|
|
173
|
+
*
|
|
174
|
+
* @param threadId - A chat message id (anything else is not internal)
|
|
175
|
+
* @param deps - Collaborators (default: the real ones)
|
|
176
|
+
* @returns Why the thread is internal, or null
|
|
177
|
+
*/
|
|
178
|
+
export declare function internalCoordinationThread(threadId: unknown, deps?: ReplyDeliveryDeps): Promise<string | null>;
|
|
131
179
|
/**
|
|
132
180
|
* The Slack place (channel + thread) the resolver gives, without posting —
|
|
133
181
|
* for files and the `[DONE]` notice. Ticket threads that do not exist yet
|
|
@@ -175,5 +223,13 @@ interface ChatChannelLookup {
|
|
|
175
223
|
* @returns The place, or null when unknown
|
|
176
224
|
*/
|
|
177
225
|
export declare function chatConversationPlace(chat: ChatChannelLookup | null, conversationId: string): string | null;
|
|
226
|
+
/**
|
|
227
|
+
* The route an agent's own messages carry onward: what it is answering now
|
|
228
|
+
* (its latest routed input; message-route.ts). Never throws.
|
|
229
|
+
*
|
|
230
|
+
* @param session - Agent session
|
|
231
|
+
* @returns The route, or null when its input names none
|
|
232
|
+
*/
|
|
233
|
+
export declare function currentRouteOf(session: string): Promise<MessageRoute | null>;
|
|
178
234
|
export {};
|
|
179
235
|
//# sourceMappingURL=reply-destination.wiring.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reply-destination.wiring.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/orc/reply-destination.wiring.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,cAAc,EAEnB,KAAK,iBAAiB,EACvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAwD,KAAK,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"reply-destination.wiring.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/orc/reply-destination.wiring.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,7 +14,8 @@ import { LoggerService } from '../core/logger.service.js';
|
|
|
14
14
|
import { AgentPromptReferenceService } from './agent-prompt-reference.service.js';
|
|
15
15
|
import { fixCommand, latestInputOf, resolveReplyDestination, } from './reply-destination-resolver.js';
|
|
16
16
|
import { defaultWorkDestinationDeps, deliverToWorkDestination } from './work-item-destination.wiring.js';
|
|
17
|
-
import {
|
|
17
|
+
import { originOfWorkItem } from './work-item-destination.js';
|
|
18
|
+
import { comparePostPlace, placeOfOwnerRoute, routeOfTrigger } from './message-route.js';
|
|
18
19
|
import { isSlackDm } from './orc-reply-route.service.js';
|
|
19
20
|
import { traceOutboundReply } from '../trace/trace-recorder.js';
|
|
20
21
|
import { stripTraceMarkers } from '../trace/trace-markers.js';
|
|
@@ -35,13 +36,12 @@ const logger = LoggerService.getInstance().createComponentLogger('ReplyDestinati
|
|
|
35
36
|
* Resolve and deliver an agent's message. Never guesses: a destination that
|
|
36
37
|
* does not take the message is reported as an error with the command to run.
|
|
37
38
|
*
|
|
38
|
-
* When the chosen place refuses the message, only
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* - the current work's Slack place → the owner's turn origin, else the DM.
|
|
39
|
+
* When the chosen place refuses the message, only the SAME place is tried
|
|
40
|
+
* by another way (each logged): the Slack thread a delegate was handed, or
|
|
41
|
+
* the Slack thread a chat thread with no row stands for. It is never moved
|
|
42
|
+
* to another place — not a new top-level post, not the owner DM, not the
|
|
43
|
+
* agent's last owner conversation (message-routing audit 2026-10-18, R2):
|
|
44
|
+
* the agent gets the error and the command that works.
|
|
45
45
|
*
|
|
46
46
|
* @param input - Agent, text, references, hints
|
|
47
47
|
* @param deps - Collaborators (default: the real ones)
|
|
@@ -71,8 +71,95 @@ export async function deliverReply(input, deps) {
|
|
|
71
71
|
recentDeliveries.set(dupKey, { at: Date.now(), delivery: result });
|
|
72
72
|
}
|
|
73
73
|
traceOutboundReply(clean, result);
|
|
74
|
+
if (result.ok)
|
|
75
|
+
await checkLandedRoute(clean, result, deps).catch(() => undefined);
|
|
74
76
|
return result;
|
|
75
77
|
}
|
|
78
|
+
/** Route check outcomes since boot (the regression check of the routing invariant). */
|
|
79
|
+
const routeCheckStats = { match: 0, mismatch: 0, unknown: 0 };
|
|
80
|
+
/**
|
|
81
|
+
* Counts of the route check since boot.
|
|
82
|
+
*
|
|
83
|
+
* @returns match / mismatch / unknown counts
|
|
84
|
+
*/
|
|
85
|
+
export function getRouteCheckStats() {
|
|
86
|
+
return { ...routeCheckStats };
|
|
87
|
+
}
|
|
88
|
+
/** Zero the route check counts (tests). */
|
|
89
|
+
export function resetRouteCheckStatsForTesting() {
|
|
90
|
+
routeCheckStats.match = 0;
|
|
91
|
+
routeCheckStats.mismatch = 0;
|
|
92
|
+
routeCheckStats.unknown = 0;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Compare where a delivered post landed with the place of the request it
|
|
96
|
+
* answers (the route of the agent's latest routed input), and log a
|
|
97
|
+
* mismatch. A post the agent addressed by an explicit reference answers that
|
|
98
|
+
* reference and is not checked; an internal message to an agent has no
|
|
99
|
+
* Slack place. Never throws, never blocks: it is a regression signal.
|
|
100
|
+
*
|
|
101
|
+
* @param input - The delivered input
|
|
102
|
+
* @param delivery - Where it landed
|
|
103
|
+
* @param deps - Collaborators (default: the real ones)
|
|
104
|
+
* @returns The outcome, or null when not checked
|
|
105
|
+
*/
|
|
106
|
+
export async function checkLandedRoute(input, delivery, deps) {
|
|
107
|
+
const dest = delivery.destination;
|
|
108
|
+
if (REFERENCE_SOURCES.has(dest.source) || dest.source === 'prompt' || delivery.agentSession)
|
|
109
|
+
return null;
|
|
110
|
+
const d = deps ?? (await defaultReplyDeliveryDeps());
|
|
111
|
+
const trigger = d.routedTrigger?.(input.session) ?? null;
|
|
112
|
+
const route = routeOfTrigger(trigger, d.resolver.turnOrigin(input.session));
|
|
113
|
+
if (!route)
|
|
114
|
+
return null;
|
|
115
|
+
const expected = await expectedPlaceOf(route, d);
|
|
116
|
+
const landed = {
|
|
117
|
+
...(delivery.conversationId ? { conversationId: delivery.conversationId } : {}),
|
|
118
|
+
...(dest.kind === 'conversation' && dest.thread ? { thread: dest.thread } : {}),
|
|
119
|
+
...(delivery.slackChannelId ? { slackChannelId: delivery.slackChannelId } : {}),
|
|
120
|
+
...(delivery.threadTs ? { threadTs: delivery.threadTs } : {}),
|
|
121
|
+
};
|
|
122
|
+
const outcome = expected === 'internal' ? 'mismatch' : comparePostPlace(expected, landed);
|
|
123
|
+
routeCheckStats[outcome] += 1;
|
|
124
|
+
if (outcome === 'mismatch') {
|
|
125
|
+
logger.warn('[RouteCheck] Post landed outside the thread of the request it answers', {
|
|
126
|
+
session: input.session,
|
|
127
|
+
route,
|
|
128
|
+
expected: expected === 'internal' ? 'internal (agent-to-agent)' : expected,
|
|
129
|
+
landed,
|
|
130
|
+
source: dest.source,
|
|
131
|
+
mismatches: routeCheckStats.mismatch,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return outcome;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The place a route's answer belongs in: an owner route's thread, a work
|
|
138
|
+
* item's owner origin, or `internal` for work an agent delegated (its answer
|
|
139
|
+
* goes to that agent, never to Slack).
|
|
140
|
+
*
|
|
141
|
+
* @param route - The route
|
|
142
|
+
* @param d - Collaborators
|
|
143
|
+
* @returns The place, `internal`, or null when unknown
|
|
144
|
+
*/
|
|
145
|
+
async function expectedPlaceOf(route, d) {
|
|
146
|
+
if (route.kind === 'owner')
|
|
147
|
+
return placeOfOwnerRoute(route);
|
|
148
|
+
const wi = await d.resolver.workItem(route.workItemId).catch(() => null);
|
|
149
|
+
const origin = wi ? originOfWorkItem(wi) : null;
|
|
150
|
+
if (!origin)
|
|
151
|
+
return null;
|
|
152
|
+
if (origin.kind === 'agent')
|
|
153
|
+
return 'internal';
|
|
154
|
+
if (origin.kind !== 'owner')
|
|
155
|
+
return null;
|
|
156
|
+
return {
|
|
157
|
+
...(origin.conversationId ? { conversationId: origin.conversationId } : {}),
|
|
158
|
+
...(origin.chatThreadId ? { thread: origin.chatThreadId } : {}),
|
|
159
|
+
...(origin.slackChannelId ? { slackChannelId: origin.slackChannelId } : {}),
|
|
160
|
+
...(origin.threadTs ? { threadTs: origin.threadTs } : {}),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
76
163
|
/**
|
|
77
164
|
* {@link deliverReply} without the run-trace record.
|
|
78
165
|
*
|
|
@@ -103,16 +190,15 @@ async function deliverReplyUntraced(input, deps) {
|
|
|
103
190
|
const delegated = await postToDelegatedThread(input, dest, d);
|
|
104
191
|
if (delegated)
|
|
105
192
|
return delegated;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return viaDm;
|
|
193
|
+
// Work handed over from another agent's DM with the owner: this agent
|
|
194
|
+
// cannot post there, and its answer is not moved to its own DM — it goes
|
|
195
|
+
// to the agent the owner asked (message-routing audit 2026-10-18, R2).
|
|
196
|
+
if (!result.ok && dest.kind === 'conversation' && (await isOthersOwnerDm(input.session, dest.conversationId, d))) {
|
|
197
|
+
const delegator = await delegatorOfWork(input, d);
|
|
198
|
+
return {
|
|
199
|
+
...result,
|
|
200
|
+
error: notDelivered(`conversation ${dest.conversationId} is another agent's DM with the owner, where you cannot post`, delegator ? `send-message --to ${delegator} "<your answer>" (${delegator} answers the owner there)` : 'send your answer to the agent who handed you this work (send-message --to <agent>)'),
|
|
201
|
+
};
|
|
116
202
|
}
|
|
117
203
|
const hintedThread = dest.source === 'hint' && dest.kind === 'conversation' ? dest.thread : undefined;
|
|
118
204
|
if (dest.source === 'hint') {
|
|
@@ -128,25 +214,10 @@ async function deliverReplyUntraced(input, deps) {
|
|
|
128
214
|
logger.warn('The conversation the agent named did not take its message', { session: input.session, error: result.error });
|
|
129
215
|
return withCardFix(result, hintedThread, d);
|
|
130
216
|
}
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (fallback) {
|
|
136
|
-
logger.warn('Current work destination did not take the message — trying the next real place', {
|
|
137
|
-
session: input.session,
|
|
138
|
-
from: dest.kind === 'work' ? dest.destination.kind : 'conversation',
|
|
139
|
-
to: fallback.kind === 'work' ? fallback.destination.kind : `conversation ${fallback.conversationId}`,
|
|
140
|
-
});
|
|
141
|
-
const retry = await attempt(input, fallback, prompt, d);
|
|
142
|
-
if (retry.ok)
|
|
143
|
-
return retry;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
// An answered reference (decision / ticket / work item / message) must not
|
|
147
|
-
// die in a thread the conversation could not post into: post straight to
|
|
148
|
-
// that Slack thread, then as a new top-level post in the room's channel.
|
|
149
|
-
if (dest.kind === 'conversation' && REFERENCE_SOURCES.has(dest.source)) {
|
|
217
|
+
// An answered reference (decision / ticket / work item / message / route)
|
|
218
|
+
// must not die in a thread the conversation could not post into: post
|
|
219
|
+
// straight to that same Slack thread. Never a new top-level post instead.
|
|
220
|
+
if (dest.kind === 'conversation' && (REFERENCE_SOURCES.has(dest.source) || dest.source === 'route' || dest.source === 'current-work' || dest.source === 'turn-origin')) {
|
|
150
221
|
for (const fallback of await referenceFallbacks(input.session, dest, d)) {
|
|
151
222
|
logger.warn('Referenced conversation did not take the reply — trying the next real place', {
|
|
152
223
|
session: input.session,
|
|
@@ -260,8 +331,8 @@ async function withCardFix(result, thread, d) {
|
|
|
260
331
|
const REFERENCE_SOURCES = new Set(['decision', 'ticket', 'work-item', 'message']);
|
|
261
332
|
/**
|
|
262
333
|
* Where an answered reference goes when its conversation refused the post:
|
|
263
|
-
* the Slack thread it names (a card posted straight to Slack has no chat
|
|
264
|
-
*
|
|
334
|
+
* the Slack thread it names (a card posted straight to Slack has no chat
|
|
335
|
+
* row). Only that same thread — never a new top-level post.
|
|
265
336
|
*
|
|
266
337
|
* @param session - Agent
|
|
267
338
|
* @param failed - The conversation destination that refused
|
|
@@ -280,13 +351,7 @@ async function referenceFallbacks(session, failed, d) {
|
|
|
280
351
|
reason: `conversation ${failed.conversationId} could not post into thread ${failed.thread} — Slack thread post`,
|
|
281
352
|
});
|
|
282
353
|
}
|
|
283
|
-
|
|
284
|
-
out.push({
|
|
285
|
-
kind: 'work',
|
|
286
|
-
destination: { kind: 'new-top-level', ...(wi ? { topic: shortTopic(wi.title) } : {}), reason: `conversation ${failed.conversationId} did not take the reply` },
|
|
287
|
-
source: failed.source,
|
|
288
|
-
reason: `conversation ${failed.conversationId} did not take the reply — new top-level post`,
|
|
289
|
-
});
|
|
354
|
+
void session;
|
|
290
355
|
return out;
|
|
291
356
|
}
|
|
292
357
|
/**
|
|
@@ -298,67 +363,45 @@ async function referenceFallbacks(session, failed, d) {
|
|
|
298
363
|
* @param prompt - The agent's prompt reference
|
|
299
364
|
* @returns A different instruction, or empty when there is none
|
|
300
365
|
*/
|
|
366
|
+
/**
|
|
367
|
+
* What to do when the place a reply resolved to cannot take it. Resending
|
|
368
|
+
* the same `reply` would resolve to the same place, so the advice names
|
|
369
|
+
* the explicit alternatives (message-routing audit 2026-10-18: refusals
|
|
370
|
+
* must be actionable).
|
|
371
|
+
*/
|
|
372
|
+
const UNPOSTABLE_ADVICE = 'reply --conversation <id> --thread <thread> "<message>" to name a place you can post in, or tell whoever gave you this work (send-message --to <session> "<message>")';
|
|
301
373
|
function retryAdvice(dest, prompt) {
|
|
302
374
|
const same = REFERENCE_SOURCES.has(dest.source) || dest.source === 'prompt';
|
|
303
375
|
const cmd = fixCommand(prompt);
|
|
304
376
|
return same && cmd !== 'reply "<your message>"' ? '' : cmd;
|
|
305
377
|
}
|
|
306
378
|
/**
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
* is opened with `Re: <work title>` so the owner sees what it answers.
|
|
379
|
+
* Whether a conversation is another agent's DM with the owner (a Slack DM
|
|
380
|
+
* the replying agent is not in).
|
|
310
381
|
*
|
|
311
|
-
* @param
|
|
312
|
-
* @param
|
|
382
|
+
* @param session - Replying agent
|
|
383
|
+
* @param conversationId - The conversation that refused
|
|
313
384
|
* @param d - Collaborators
|
|
314
|
-
* @returns
|
|
385
|
+
* @returns True for another agent's owner DM
|
|
315
386
|
*/
|
|
316
|
-
async function
|
|
317
|
-
|
|
318
|
-
return null;
|
|
319
|
-
const slack = d.resolver.slackChannelOfConversation(failed.conversationId);
|
|
387
|
+
async function isOthersOwnerDm(session, conversationId, d) {
|
|
388
|
+
const slack = d.resolver.slackChannelOfConversation(conversationId);
|
|
320
389
|
if (!slack || !isSlackDm(slack))
|
|
321
|
-
return
|
|
322
|
-
|
|
323
|
-
return null;
|
|
324
|
-
const dm = await d.resolver.ownerDm(input.session).catch(() => null);
|
|
325
|
-
if (!dm || dm === failed.conversationId)
|
|
326
|
-
return null;
|
|
327
|
-
const wi = input.reference?.workItemId
|
|
328
|
-
? await d.resolver.workItem(input.reference.workItemId).catch(() => null)
|
|
329
|
-
: currentWorkItemOf(await d.resolver.poolItems().catch(() => []), input.session);
|
|
330
|
-
const topic = wi ? `Re: ${shortTopic(wi.title)}` : undefined;
|
|
331
|
-
return {
|
|
332
|
-
dest: { kind: 'conversation', conversationId: dm, source: 'owner-dm', reason: 'the owner asked in a DM with another agent — your DM with the owner' },
|
|
333
|
-
content: withTopicLine(topic, input.content),
|
|
334
|
-
};
|
|
390
|
+
return false;
|
|
391
|
+
return !(await d.resolver.ownsConversation(session, conversationId).catch(() => true));
|
|
335
392
|
}
|
|
336
393
|
/**
|
|
337
|
-
* The
|
|
394
|
+
* The agent that handed the replier the work it is answering, when known.
|
|
338
395
|
*
|
|
339
|
-
* @param
|
|
340
|
-
* @param failed - The destination that refused
|
|
396
|
+
* @param input - Agent, reference
|
|
341
397
|
* @param d - Collaborators
|
|
342
|
-
* @returns
|
|
398
|
+
* @returns The delegator's session, or null
|
|
343
399
|
*/
|
|
344
|
-
async function
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
kind: 'work',
|
|
350
|
-
destination: { kind: 'new-top-level', ...(wi ? { topic: shortTopic(wi.title) } : {}), reason: `conversation ${failed.conversationId} did not take the reply` },
|
|
351
|
-
source: 'current-work',
|
|
352
|
-
reason: `conversation ${failed.conversationId} did not take the reply — new top-level post`,
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
const origin = d.resolver.turnOrigin(session);
|
|
356
|
-
if (origin) {
|
|
357
|
-
const thread = origin.slackThreadKey ?? origin.chatThreadId;
|
|
358
|
-
return { kind: 'conversation', conversationId: origin.conversationId, ...(thread ? { thread } : {}), source: 'turn-origin', reason: 'the work destination did not take the reply — your last owner conversation' };
|
|
359
|
-
}
|
|
360
|
-
const dm = await d.resolver.ownerDm(session).catch(() => null);
|
|
361
|
-
return dm ? { kind: 'conversation', conversationId: dm, source: 'owner-dm', reason: 'the work destination did not take the reply — your DM with the owner' } : null;
|
|
400
|
+
async function delegatorOfWork(input, d) {
|
|
401
|
+
const id = input.reference?.workItemId ?? d.resolver.latestInput?.(input.session)?.workItemId;
|
|
402
|
+
const wi = id ? await d.resolver.workItem(id).catch(() => null) : null;
|
|
403
|
+
const by = wi?.metadata?.delegatedBy;
|
|
404
|
+
return typeof by === 'string' && by && by !== input.session ? by : null;
|
|
362
405
|
}
|
|
363
406
|
/**
|
|
364
407
|
* Why the agent is posting: the provenance record for the row and the text
|
|
@@ -371,15 +414,21 @@ async function currentWorkFallback(session, failed, d) {
|
|
|
371
414
|
* @param d - Collaborators
|
|
372
415
|
* @returns Metadata and the text to post, or null when no trigger is known
|
|
373
416
|
*/
|
|
374
|
-
async function provenanceOf(input, postedConversationId, d) {
|
|
417
|
+
async function provenanceOf(input, postedConversationId, d, answering) {
|
|
375
418
|
try {
|
|
376
|
-
const trigger =
|
|
419
|
+
const trigger = triggerOfAnsweredInput(input.session, d, answering);
|
|
377
420
|
if (!trigger)
|
|
378
421
|
return null;
|
|
379
422
|
const workItem = trigger.workItemId ? await d.resolver.workItem(trigger.workItemId).catch(() => null) : null;
|
|
423
|
+
// The post answers the owner's message in the conversation it lands in
|
|
424
|
+
// when the owner wrote to the agent there — even when a newer owner
|
|
425
|
+
// message elsewhere is the agent's latest (R8).
|
|
426
|
+
const answersOwnerHere = !!postedConversationId &&
|
|
427
|
+
((d.ownerAskedRecently?.(input.session, postedConversationId) ?? false) ||
|
|
428
|
+
(trigger.triggerKind === 'owner_message' && trigger.conversationId !== postedConversationId && (d.ownerAskedEarlier?.(input.session, postedConversationId) ?? false)));
|
|
380
429
|
const phrase = triggeredByPhrase(trigger, {
|
|
381
430
|
...(postedConversationId ? { postedConversationId } : {}),
|
|
382
|
-
ownerAskedHere:
|
|
431
|
+
ownerAskedHere: answersOwnerHere,
|
|
383
432
|
workItem,
|
|
384
433
|
...(d.conversationPlace ? { conversationPlace: d.conversationPlace } : {}),
|
|
385
434
|
});
|
|
@@ -394,6 +443,23 @@ async function provenanceOf(input, postedConversationId, d) {
|
|
|
394
443
|
return null;
|
|
395
444
|
}
|
|
396
445
|
}
|
|
446
|
+
/**
|
|
447
|
+
* The trigger of the input a post answers: for a post addressed to a work
|
|
448
|
+
* item, that work item's dispatch (the session's own trigger when it is that
|
|
449
|
+
* dispatch), never whatever was delivered to the agent last (R8).
|
|
450
|
+
*
|
|
451
|
+
* @param session - Agent
|
|
452
|
+
* @param d - Collaborators
|
|
453
|
+
* @param answering - The work item the post names, when it names one
|
|
454
|
+
* @returns The trigger, or null
|
|
455
|
+
*/
|
|
456
|
+
function triggerOfAnsweredInput(session, d, answering) {
|
|
457
|
+
const current = d.turnTrigger?.(session) ?? null;
|
|
458
|
+
const id = answering?.workItemId;
|
|
459
|
+
if (!id || current?.workItemId === id)
|
|
460
|
+
return current;
|
|
461
|
+
return { triggerKind: 'dispatch', workItemId: id, rootKind: 'dispatch', rootRef: id, viaAgents: [], at: Date.now(), ...(current?.traceId ? { traceId: current.traceId } : {}) };
|
|
462
|
+
}
|
|
397
463
|
/**
|
|
398
464
|
* Provenance for a post an agent makes straight into a conversation
|
|
399
465
|
* (`reply-channel`, `POST /chat/channels/:id/messages`), outside
|
|
@@ -431,7 +497,8 @@ async function attempt(input, dest, prompt, d) {
|
|
|
431
497
|
const ticket = dest.source === 'ticket' ? input.reference?.ticket : dest.source === 'prompt' ? prompt?.ticket : undefined;
|
|
432
498
|
const deliversTicket = ticket ? { [REPLY_ROUTING_CONSTANTS.DELIVERS_TICKET_METADATA_KEY]: ticket } : undefined;
|
|
433
499
|
const answeredReference = dest.source === 'prompt' || dest.source === 'ticket' || dest.source === 'decision' || dest.source === 'work-item' || dest.source === 'message';
|
|
434
|
-
const
|
|
500
|
+
const answeredWorkItem = dest.source === 'work-item' ? input.reference?.workItemId : dest.source === 'prompt' ? prompt?.workItemId : undefined;
|
|
501
|
+
const provenance = await provenanceOf(input, dest.kind === 'conversation' ? dest.conversationId : undefined, d, answeredWorkItem ? { workItemId: answeredWorkItem } : undefined);
|
|
435
502
|
const metadata = { ...(deliversTicket ?? {}), ...(provenance?.metadata ?? {}) };
|
|
436
503
|
if (provenance?.content !== undefined)
|
|
437
504
|
input = { ...input, content: provenance.content };
|
|
@@ -461,7 +528,9 @@ async function attempt(input, dest, prompt, d) {
|
|
|
461
528
|
const why = member
|
|
462
529
|
? `conversation ${dest.conversationId} (${dest.reason}) could not post it into its Slack thread`
|
|
463
530
|
: `conversation ${dest.conversationId} (${dest.reason}) did not take it — you are not a member there`;
|
|
464
|
-
|
|
531
|
+
// A work item's place is fixed by the item: resending the same reply lands there again.
|
|
532
|
+
const advice = dest.source === 'work-item' || dest.source === 'current-work' || dest.source === 'route' ? UNPOSTABLE_ADVICE : retryAdvice(dest, prompt);
|
|
533
|
+
return { ok: false, destination: dest, error: notDelivered(why, advice) };
|
|
465
534
|
}
|
|
466
535
|
if (answeredReference && input.interim !== true)
|
|
467
536
|
AgentPromptReferenceService.getInstance().clear(input.session);
|
|
@@ -479,7 +548,7 @@ async function attempt(input, dest, prompt, d) {
|
|
|
479
548
|
const posted = await deliverToWorkDestination(input.session, dest.destination, input.content, wd);
|
|
480
549
|
if (!posted) {
|
|
481
550
|
const why = dest.destination.kind === 'agent' ? `${dest.reason}: ${dest.destination.sessionName} could not be reached` : `${dest.reason}: there is no Slack channel to post in`;
|
|
482
|
-
return { ok: false, destination: dest, error: notDelivered(why,
|
|
551
|
+
return { ok: false, destination: dest, error: notDelivered(why, UNPOSTABLE_ADVICE) };
|
|
483
552
|
}
|
|
484
553
|
if (answeredReference && input.interim !== true)
|
|
485
554
|
AgentPromptReferenceService.getInstance().clear(input.session);
|
|
@@ -493,9 +562,35 @@ async function attempt(input, dest, prompt, d) {
|
|
|
493
562
|
};
|
|
494
563
|
}
|
|
495
564
|
catch (err) {
|
|
496
|
-
return { ok: false, destination: dest, error: notDelivered(`posting to Slack failed (${err instanceof Error ? err.message : String(err)})`,
|
|
565
|
+
return { ok: false, destination: dest, error: notDelivered(`posting to Slack failed (${err instanceof Error ? err.message : String(err)})`, UNPOSTABLE_ADVICE) };
|
|
497
566
|
}
|
|
498
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* Whether a chat thread is agent-to-agent coordination kept inside Crewly
|
|
570
|
+
* (crewly#1340): the message, or the root of its thread, carries
|
|
571
|
+
* `agentCoordinationInternal`. `reply-channel` keeps a post in such a thread
|
|
572
|
+
* internal; a file must not go to the Slack thread key stored on that
|
|
573
|
+
* message either (Pia's v3/v4 videos for Ella landed in Steve's thread,
|
|
574
|
+
* 2026-10-18).
|
|
575
|
+
*
|
|
576
|
+
* @param threadId - A chat message id (anything else is not internal)
|
|
577
|
+
* @param deps - Collaborators (default: the real ones)
|
|
578
|
+
* @returns Why the thread is internal, or null
|
|
579
|
+
*/
|
|
580
|
+
export async function internalCoordinationThread(threadId, deps) {
|
|
581
|
+
if (typeof threadId !== 'string' || !threadId.trim() || parseSlackThreadKey(threadId))
|
|
582
|
+
return null;
|
|
583
|
+
const d = deps ?? (await defaultReplyDeliveryDeps());
|
|
584
|
+
const { AGENT_COORDINATION_CONSTANTS } = await import('../../constants.js');
|
|
585
|
+
const reasonOf = (id) => {
|
|
586
|
+
const v = id ? d.resolver.getMessage(id)?.metadata?.[AGENT_COORDINATION_CONSTANTS.INTERNAL_METADATA_KEY] : undefined;
|
|
587
|
+
return typeof v === 'string' && v ? v : null;
|
|
588
|
+
};
|
|
589
|
+
const msg = d.resolver.getMessage(threadId.trim());
|
|
590
|
+
if (!msg)
|
|
591
|
+
return null;
|
|
592
|
+
return reasonOf(msg.id) ?? reasonOf(msg.threadId);
|
|
593
|
+
}
|
|
499
594
|
/**
|
|
500
595
|
* The Slack place (channel + thread) the resolver gives, without posting —
|
|
501
596
|
* for files and the `[DONE]` notice. Ticket threads that do not exist yet
|
|
@@ -747,7 +842,9 @@ export async function defaultReplyDeliveryDeps() {
|
|
|
747
842
|
});
|
|
748
843
|
},
|
|
749
844
|
surfacedOwnerConversations: (session) => SubAgentMessageQueue.getInstance().surfacedUnansweredConversations(session),
|
|
750
|
-
|
|
845
|
+
// What the agent answers: the latest input that names it, not a reminder
|
|
846
|
+
// or notice delivered since (message-routing audit 2026-10-18).
|
|
847
|
+
latestInput: (session) => latestInputOf(getTurnTriggerTracker().routedTrigger(session)),
|
|
751
848
|
now: () => Date.now(),
|
|
752
849
|
};
|
|
753
850
|
const { deliverAgentReplyToConversation } = await import('../../controllers/chat/chat.controller.js');
|
|
@@ -768,6 +865,8 @@ export async function defaultReplyDeliveryDeps() {
|
|
|
768
865
|
},
|
|
769
866
|
turnTrigger: (session) => getTurnTriggerTracker().trigger(session),
|
|
770
867
|
ownerAskedRecently: (session, conversationId) => getTurnTriggerTracker().ownerAskedRecently(session, conversationId),
|
|
868
|
+
ownerAskedEarlier: (session, conversationId) => getTurnTriggerTracker().ownerAskedWithin(session, conversationId, TURN_TRIGGER_CONSTANTS.FRESH_MS),
|
|
869
|
+
routedTrigger: (session) => getTurnTriggerTracker().routedTrigger(session),
|
|
771
870
|
currentTrace: (session) => getTraceContext().currentTrace(session),
|
|
772
871
|
conversationPlace: (id) => chatConversationPlace(chatLookup, id),
|
|
773
872
|
};
|
|
@@ -807,4 +906,20 @@ export function chatConversationPlace(chat, conversationId) {
|
|
|
807
906
|
return null;
|
|
808
907
|
}
|
|
809
908
|
}
|
|
909
|
+
/**
|
|
910
|
+
* The route an agent's own messages carry onward: what it is answering now
|
|
911
|
+
* (its latest routed input; message-route.ts). Never throws.
|
|
912
|
+
*
|
|
913
|
+
* @param session - Agent session
|
|
914
|
+
* @returns The route, or null when its input names none
|
|
915
|
+
*/
|
|
916
|
+
export async function currentRouteOf(session) {
|
|
917
|
+
try {
|
|
918
|
+
const { OrcReplyRouteService } = await import('./orc-reply-route.service.js');
|
|
919
|
+
return routeOfTrigger(getTurnTriggerTracker().routedTrigger(session), OrcReplyRouteService.getInstance().getLastOrigin(session));
|
|
920
|
+
}
|
|
921
|
+
catch {
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
810
925
|
//# sourceMappingURL=reply-destination.wiring.js.map
|