agent-relay 6.0.22 → 6.2.1
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/index.cjs +338 -49
- package/dist/src/cli/bootstrap.d.ts.map +1 -1
- package/dist/src/cli/bootstrap.js +62 -0
- package/dist/src/cli/bootstrap.js.map +1 -1
- package/dist/src/cli/commands/agent-management.d.ts +8 -0
- package/dist/src/cli/commands/agent-management.d.ts.map +1 -1
- package/dist/src/cli/commands/agent-management.js +34 -0
- package/dist/src/cli/commands/agent-management.js.map +1 -1
- package/dist/src/cli/commands/drive.d.ts +222 -0
- package/dist/src/cli/commands/drive.d.ts.map +1 -0
- package/dist/src/cli/commands/drive.js +565 -0
- package/dist/src/cli/commands/drive.js.map +1 -0
- package/dist/src/cli/commands/messaging.d.ts +25 -0
- package/dist/src/cli/commands/messaging.d.ts.map +1 -1
- package/dist/src/cli/commands/messaging.js +702 -138
- package/dist/src/cli/commands/messaging.js.map +1 -1
- package/dist/src/cli/commands/new.d.ts +112 -0
- package/dist/src/cli/commands/new.d.ts.map +1 -0
- package/dist/src/cli/commands/new.js +189 -0
- package/dist/src/cli/commands/new.js.map +1 -0
- package/dist/src/cli/commands/on/provision.d.ts +1 -1
- package/dist/src/cli/commands/on/provision.d.ts.map +1 -1
- package/dist/src/cli/commands/on/provision.js +1 -1
- package/dist/src/cli/commands/on/provision.js.map +1 -1
- package/dist/src/cli/commands/on/start.d.ts +1 -1
- package/dist/src/cli/commands/on/start.d.ts.map +1 -1
- package/dist/src/cli/commands/on/start.js +2 -2
- package/dist/src/cli/commands/on/start.js.map +1 -1
- package/dist/src/cli/commands/passthrough.d.ts +142 -0
- package/dist/src/cli/commands/passthrough.d.ts.map +1 -0
- package/dist/src/cli/commands/passthrough.js +398 -0
- package/dist/src/cli/commands/passthrough.js.map +1 -0
- package/dist/src/cli/commands/rm.d.ts +52 -0
- package/dist/src/cli/commands/rm.d.ts.map +1 -0
- package/dist/src/cli/commands/rm.js +96 -0
- package/dist/src/cli/commands/rm.js.map +1 -0
- package/dist/src/cli/commands/view.d.ts +98 -0
- package/dist/src/cli/commands/view.d.ts.map +1 -0
- package/dist/src/cli/commands/view.js +238 -0
- package/dist/src/cli/commands/view.js.map +1 -0
- package/dist/src/cli/lib/agent-management-listing.d.ts +35 -7
- package/dist/src/cli/lib/agent-management-listing.d.ts.map +1 -1
- package/dist/src/cli/lib/agent-management-listing.js +188 -40
- package/dist/src/cli/lib/agent-management-listing.js.map +1 -1
- package/dist/src/cli/lib/attach.d.ts +56 -0
- package/dist/src/cli/lib/attach.d.ts.map +1 -0
- package/dist/src/cli/lib/attach.js +73 -0
- package/dist/src/cli/lib/attach.js.map +1 -0
- package/dist/src/cli/lib/broker-connection.d.ts +40 -0
- package/dist/src/cli/lib/broker-connection.d.ts.map +1 -0
- package/dist/src/cli/lib/broker-connection.js +82 -0
- package/dist/src/cli/lib/broker-connection.js.map +1 -0
- package/dist/src/cli/lib/formatting.d.ts +4 -0
- package/dist/src/cli/lib/formatting.d.ts.map +1 -1
- package/dist/src/cli/lib/formatting.js +31 -1
- package/dist/src/cli/lib/formatting.js.map +1 -1
- package/dist/src/cli/lib/sdk-client.d.ts +9 -0
- package/dist/src/cli/lib/sdk-client.d.ts.map +1 -0
- package/dist/src/cli/lib/sdk-client.js +28 -0
- package/dist/src/cli/lib/sdk-client.js.map +1 -0
- package/dist/src/cli/lib/spawn-and-attach.d.ts +132 -0
- package/dist/src/cli/lib/spawn-and-attach.d.ts.map +1 -0
- package/dist/src/cli/lib/spawn-and-attach.js +334 -0
- package/dist/src/cli/lib/spawn-and-attach.js.map +1 -0
- package/package.json +12 -10
- package/dist/packages/cloud/src/api-client.d.ts +0 -33
- package/dist/packages/cloud/src/api-client.d.ts.map +0 -1
- package/dist/packages/cloud/src/api-client.js +0 -123
- package/dist/packages/cloud/src/api-client.js.map +0 -1
- package/dist/packages/cloud/src/auth.d.ts +0 -13
- package/dist/packages/cloud/src/auth.d.ts.map +0 -1
- package/dist/packages/cloud/src/auth.js +0 -299
- package/dist/packages/cloud/src/auth.js.map +0 -1
- package/dist/packages/cloud/src/connect.d.ts +0 -45
- package/dist/packages/cloud/src/connect.d.ts.map +0 -1
- package/dist/packages/cloud/src/connect.js +0 -166
- package/dist/packages/cloud/src/connect.js.map +0 -1
- package/dist/packages/cloud/src/index.d.ts +0 -10
- package/dist/packages/cloud/src/index.d.ts.map +0 -1
- package/dist/packages/cloud/src/index.js +0 -10
- package/dist/packages/cloud/src/index.js.map +0 -1
- package/dist/packages/cloud/src/lib/ssh-interactive.d.ts +0 -70
- package/dist/packages/cloud/src/lib/ssh-interactive.d.ts.map +0 -1
- package/dist/packages/cloud/src/lib/ssh-interactive.js +0 -440
- package/dist/packages/cloud/src/lib/ssh-interactive.js.map +0 -1
- package/dist/packages/cloud/src/lib/ssh-runtime.d.ts +0 -35
- package/dist/packages/cloud/src/lib/ssh-runtime.d.ts.map +0 -1
- package/dist/packages/cloud/src/lib/ssh-runtime.js +0 -52
- package/dist/packages/cloud/src/lib/ssh-runtime.js.map +0 -1
- package/dist/packages/cloud/src/proactive-runtime.d.ts +0 -24
- package/dist/packages/cloud/src/proactive-runtime.d.ts.map +0 -1
- package/dist/packages/cloud/src/proactive-runtime.js +0 -315
- package/dist/packages/cloud/src/proactive-runtime.js.map +0 -1
- package/dist/packages/cloud/src/types.d.ts +0 -200
- package/dist/packages/cloud/src/types.d.ts.map +0 -1
- package/dist/packages/cloud/src/types.js +0 -12
- package/dist/packages/cloud/src/types.js.map +0 -1
- package/dist/packages/cloud/src/workflows.d.ts +0 -65
- package/dist/packages/cloud/src/workflows.d.ts.map +0 -1
- package/dist/packages/cloud/src/workflows.js +0 -892
- package/dist/packages/cloud/src/workflows.js.map +0 -1
- package/dist/packages/cloud/src/workspaces.d.ts +0 -11
- package/dist/packages/cloud/src/workspaces.d.ts.map +0 -1
- package/dist/packages/cloud/src/workspaces.js +0 -146
- package/dist/packages/cloud/src/workspaces.js.map +0 -1
- package/dist/packages/sdk/src/provisioner/local-jwks.d.ts +0 -25
- package/dist/packages/sdk/src/provisioner/local-jwks.d.ts.map +0 -1
- package/dist/packages/sdk/src/provisioner/local-jwks.js +0 -70
- package/dist/packages/sdk/src/provisioner/local-jwks.js.map +0 -1
- package/dist/packages/sdk/src/provisioner/seeder.d.ts +0 -17
- package/dist/packages/sdk/src/provisioner/seeder.d.ts.map +0 -1
- package/dist/packages/sdk/src/provisioner/seeder.js +0 -419
- package/dist/packages/sdk/src/provisioner/seeder.js.map +0 -1
- package/dist/packages/sdk/src/provisioner/token.d.ts +0 -41
- package/dist/packages/sdk/src/provisioner/token.d.ts.map +0 -1
- package/dist/packages/sdk/src/provisioner/token.js +0 -77
- package/dist/packages/sdk/src/provisioner/token.js.map +0 -1
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { RelayCast, AgentRelayClient } from '@agent-relay/sdk';
|
|
2
2
|
import { getProjectPaths } from '@agent-relay/config';
|
|
3
3
|
import { defaultExit } from '../lib/exit.js';
|
|
4
|
-
import { parseSince } from '../lib/formatting.js';
|
|
4
|
+
import { parseSince, sanitizeForTerminal, sanitizeForTerminalLine } from '../lib/formatting.js';
|
|
5
|
+
const MAX_DM_FETCH_LIMIT = 1000;
|
|
6
|
+
const MARK_READ_CONCURRENCY = 8;
|
|
7
|
+
const HISTORY_DM_CONVERSATION_SCAN_LIMIT = 50;
|
|
8
|
+
const HISTORY_DM_PER_CONVERSATION_FETCH_LIMIT = 100;
|
|
9
|
+
const INBOX_UNREAD_DM_FETCH_CONVERSATION_LIMIT = 50;
|
|
10
|
+
const INBOX_DM_PREVIEW_FETCH_LIMIT = 10;
|
|
11
|
+
const FALLBACK_DM_CREATED_AT = '1970-01-01T00:00:00.000Z';
|
|
5
12
|
function isPresent(value) {
|
|
6
13
|
return value !== null && value !== undefined;
|
|
7
14
|
}
|
|
@@ -13,6 +20,9 @@ function readString(...values) {
|
|
|
13
20
|
}
|
|
14
21
|
return undefined;
|
|
15
22
|
}
|
|
23
|
+
function readText(value) {
|
|
24
|
+
return typeof value === 'string' ? value : '';
|
|
25
|
+
}
|
|
16
26
|
function readNumber(...values) {
|
|
17
27
|
for (const value of values) {
|
|
18
28
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
@@ -21,6 +31,149 @@ function readNumber(...values) {
|
|
|
21
31
|
}
|
|
22
32
|
return undefined;
|
|
23
33
|
}
|
|
34
|
+
function readBoolean(...values) {
|
|
35
|
+
for (const value of values) {
|
|
36
|
+
if (typeof value === 'boolean') {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
function formatErrorDetail(err) {
|
|
43
|
+
return sanitizeForTerminalLine(err instanceof Error ? err.message : String(err));
|
|
44
|
+
}
|
|
45
|
+
function getDefaultOrchestratorName() {
|
|
46
|
+
return process.env.AGENT_RELAY_ORCHESTRATOR_NAME?.trim() || 'orchestrator';
|
|
47
|
+
}
|
|
48
|
+
function getAllowedReadAgentNames() {
|
|
49
|
+
const allowed = new Set([getDefaultOrchestratorName()]);
|
|
50
|
+
for (const name of (process.env.AGENT_RELAY_ALLOWED_READ_IDENTITIES ?? '').split(',')) {
|
|
51
|
+
const trimmed = name.trim();
|
|
52
|
+
if (trimmed) {
|
|
53
|
+
allowed.add(trimmed);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return allowed;
|
|
57
|
+
}
|
|
58
|
+
function isAuthorizedReadIdentity(agentName) {
|
|
59
|
+
return getAllowedReadAgentNames().has(agentName);
|
|
60
|
+
}
|
|
61
|
+
function reportUnauthorizedReadIdentity(deps, agentName) {
|
|
62
|
+
deps.error(`Refusing to read as ${sanitizeForTerminalLine(agentName)}: read identities must be the configured orchestrator or listed in AGENT_RELAY_ALLOWED_READ_IDENTITIES.`);
|
|
63
|
+
deps.exit(1);
|
|
64
|
+
}
|
|
65
|
+
function parseMessageLimit(value, defaultValue = 50) {
|
|
66
|
+
if (value === undefined) {
|
|
67
|
+
return defaultValue;
|
|
68
|
+
}
|
|
69
|
+
const trimmed = String(value).trim();
|
|
70
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
71
|
+
throw new Error(`Invalid --limit value: ${value}`);
|
|
72
|
+
}
|
|
73
|
+
const parsed = Number(trimmed);
|
|
74
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1) {
|
|
75
|
+
throw new Error(`Invalid --limit value: ${value}`);
|
|
76
|
+
}
|
|
77
|
+
return Math.min(parsed, MAX_DM_FETCH_LIMIT);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* `--since` accepts either a time (`5m`, `1h`, ISO-8601) or a message-id
|
|
81
|
+
* cursor. A cursor returns only messages strictly AFTER that id in
|
|
82
|
+
* chronological order, so a polling caller (`--since <lastId>`) never
|
|
83
|
+
* re-receives a message it already saw — the spec's stale-replay fix.
|
|
84
|
+
*/
|
|
85
|
+
function resolveSince(value) {
|
|
86
|
+
if (value === undefined || !String(value).trim())
|
|
87
|
+
return { kind: 'none' };
|
|
88
|
+
const trimmed = String(value).trim();
|
|
89
|
+
const ts = parseSince(trimmed);
|
|
90
|
+
if (ts !== undefined)
|
|
91
|
+
return { kind: 'time', ts };
|
|
92
|
+
if (/^-\d+(?:[smhd]|sec|secs|second|seconds|min|mins|minute|minutes|hour|hours|day|days)$/i.test(trimmed)) {
|
|
93
|
+
throw new Error(`Invalid --since value: ${value}`);
|
|
94
|
+
}
|
|
95
|
+
if (/^\d+(?:sec|secs|second|seconds|min|mins|minute|minutes|hour|hours|day|days)$/i.test(trimmed)) {
|
|
96
|
+
throw new Error(`Invalid --since value: ${value}`);
|
|
97
|
+
}
|
|
98
|
+
// Not a time. Treat as an opaque message-id cursor. Whitespace means it is
|
|
99
|
+
// neither a valid time nor a plausible id — surface the original error.
|
|
100
|
+
if (/\s/.test(trimmed)) {
|
|
101
|
+
throw new Error(`Invalid --since value: ${value}`);
|
|
102
|
+
}
|
|
103
|
+
return { kind: 'cursor', id: trimmed };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Drop everything up to and including the cursor message. If the cursor id
|
|
107
|
+
* is not in the (already chronologically sorted) window, return the available
|
|
108
|
+
* window so callers do not silently miss a burst that pushed the cursor out of
|
|
109
|
+
* the bounded fetch. `messages` MUST be sorted oldest→newest.
|
|
110
|
+
*/
|
|
111
|
+
function sliceAfterCursor(messages, cursorId) {
|
|
112
|
+
let cut = -1;
|
|
113
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
114
|
+
if (messages[i].id === cursorId) {
|
|
115
|
+
cut = i;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (cut === -1)
|
|
120
|
+
return messages;
|
|
121
|
+
return messages.slice(cut + 1);
|
|
122
|
+
}
|
|
123
|
+
function normalizeUnreadCount(value) {
|
|
124
|
+
if (!Number.isFinite(value) || value === undefined || value < 0) {
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
return Math.floor(value);
|
|
128
|
+
}
|
|
129
|
+
function shellQuote(value) {
|
|
130
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
131
|
+
}
|
|
132
|
+
function renderShellQuotedForTerminal(value) {
|
|
133
|
+
return shellQuote(sanitizeForTerminalLine(value));
|
|
134
|
+
}
|
|
135
|
+
async function disconnectRelaycastClient(client) {
|
|
136
|
+
try {
|
|
137
|
+
await client?.disconnect?.();
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Best-effort cleanup must not mask the command result.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function getDmParticipantName(participant) {
|
|
144
|
+
return readString(participant.agentName, participant.agent_name);
|
|
145
|
+
}
|
|
146
|
+
function getDmParticipantNames(conversation) {
|
|
147
|
+
return conversation.participants.map(getDmParticipantName).filter(isPresent);
|
|
148
|
+
}
|
|
149
|
+
const IMPLICIT_DM_PARTICIPANT_NAMES = new Set(['system', 'relay', 'relaycast', 'bot']);
|
|
150
|
+
function isImplicitDmParticipant(name) {
|
|
151
|
+
return IMPLICIT_DM_PARTICIPANT_NAMES.has(name.trim().toLowerCase());
|
|
152
|
+
}
|
|
153
|
+
function hasCompatibleDirectDmParticipants(conversation, readerName, agentName, allowReaderOmitted) {
|
|
154
|
+
const participantNames = new Set(getDmParticipantNames(conversation));
|
|
155
|
+
if (!participantNames.has(agentName)) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
const hasReader = participantNames.has(readerName);
|
|
159
|
+
if (!hasReader && !allowReaderOmitted) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
if (!hasReader && allowReaderOmitted) {
|
|
163
|
+
return [...participantNames].every((name) => name === agentName || isImplicitDmParticipant(name));
|
|
164
|
+
}
|
|
165
|
+
return [...participantNames].every((name) => name === readerName || name === agentName || isImplicitDmParticipant(name));
|
|
166
|
+
}
|
|
167
|
+
function findDirectDmConversation(conversations, readerName, agentName) {
|
|
168
|
+
return (conversations.find((conversation) => {
|
|
169
|
+
const dmType = readString(conversation.type, conversation.dm_type);
|
|
170
|
+
return dmType === '1:1' && hasCompatibleDirectDmParticipants(conversation, readerName, agentName, true);
|
|
171
|
+
}) ??
|
|
172
|
+
conversations.find((conversation) => {
|
|
173
|
+
const dmType = readString(conversation.type, conversation.dm_type);
|
|
174
|
+
return !dmType && hasCompatibleDirectDmParticipants(conversation, readerName, agentName, false);
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
24
177
|
function normalizeIsoTimestamp(value) {
|
|
25
178
|
if (typeof value !== 'string' || !value.trim()) {
|
|
26
179
|
return undefined;
|
|
@@ -40,10 +193,89 @@ function normalizeMessage(message) {
|
|
|
40
193
|
return {
|
|
41
194
|
id: message.id,
|
|
42
195
|
agentName,
|
|
43
|
-
text: message.text,
|
|
196
|
+
text: readText(message.text),
|
|
197
|
+
createdAt,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function normalizeDmMessage(message, defaults = {}) {
|
|
201
|
+
const agentName = readString(message.agentName, message.agent_name) ?? defaults.agentName;
|
|
202
|
+
const createdAt = normalizeIsoTimestamp(readString(message.createdAt, message.created_at)) ?? defaults.createdAt;
|
|
203
|
+
if (!agentName || !createdAt) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
const explicitUnread = readBoolean(message.unread, message.isUnread, message.is_unread);
|
|
207
|
+
const explicitRead = readBoolean(message.read, message.isRead, message.is_read);
|
|
208
|
+
return {
|
|
209
|
+
id: message.id,
|
|
210
|
+
agentName,
|
|
211
|
+
text: readText(message.text),
|
|
44
212
|
createdAt,
|
|
213
|
+
unread: explicitUnread ?? (explicitRead === undefined ? undefined : !explicitRead),
|
|
45
214
|
};
|
|
46
215
|
}
|
|
216
|
+
function sortDmMessagesChronologically(messages) {
|
|
217
|
+
return [...messages].sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
|
|
218
|
+
}
|
|
219
|
+
function renderTranscriptMessage(log, message) {
|
|
220
|
+
const agentName = sanitizeForTerminalLine(message.agentName);
|
|
221
|
+
const createdAt = sanitizeForTerminalLine(message.createdAt);
|
|
222
|
+
const lines = message.text.split(/\r?\n/).map(sanitizeForTerminal);
|
|
223
|
+
if (lines.length === 1) {
|
|
224
|
+
log(`[${createdAt}] ${agentName}: ${lines[0]}`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
log(`[${createdAt}] ${agentName}:`);
|
|
228
|
+
for (const line of lines) {
|
|
229
|
+
log(` ${line}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function isInboundUnreadDmMessage(message, senderName) {
|
|
233
|
+
return message.agentName === senderName;
|
|
234
|
+
}
|
|
235
|
+
function sortUnreadDmMessagesMostRecentFirst(messages) {
|
|
236
|
+
return [...messages].sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
|
|
237
|
+
}
|
|
238
|
+
function getFilteredDmFetchLimit(displayLimit, unreadCount = 0) {
|
|
239
|
+
return Math.min(Math.max(displayLimit * 2, normalizeUnreadCount(unreadCount), 100), MAX_DM_FETCH_LIMIT);
|
|
240
|
+
}
|
|
241
|
+
function selectUnreadCandidates(messages, unreadCount) {
|
|
242
|
+
const normalizedUnreadCount = normalizeUnreadCount(unreadCount);
|
|
243
|
+
const explicitUnread = messages.filter((message) => message.unread === true);
|
|
244
|
+
if (explicitUnread.length > 0 || normalizedUnreadCount === 0) {
|
|
245
|
+
return explicitUnread;
|
|
246
|
+
}
|
|
247
|
+
const unknownReadState = messages.filter((message) => message.unread === undefined);
|
|
248
|
+
if (unknownReadState.length === 0) {
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
return sortDmMessagesChronologically(unknownReadState).slice(-normalizedUnreadCount);
|
|
252
|
+
}
|
|
253
|
+
function renderUnreadDmMessage(log, message, senderName) {
|
|
254
|
+
if (message.diagnostic) {
|
|
255
|
+
log(` ${sanitizeForTerminalLine(message.diagnostic)}`);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const lines = message.text.split(/\r?\n/).map(sanitizeForTerminal);
|
|
259
|
+
const displaySender = sanitizeForTerminalLine(message.agentName || senderName);
|
|
260
|
+
const createdAt = sanitizeForTerminalLine(message.createdAt);
|
|
261
|
+
if (lines.length === 1) {
|
|
262
|
+
log(` [${createdAt}] ${displaySender}: ${lines[0]}`);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
log(` [${createdAt}] ${displaySender}:`);
|
|
266
|
+
for (const line of lines) {
|
|
267
|
+
log(` ${line}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function getLastMessageDirection(message, dmFrom, readerName) {
|
|
271
|
+
if (message.agentName === dmFrom) {
|
|
272
|
+
return 'inbound';
|
|
273
|
+
}
|
|
274
|
+
if (message.agentName === readerName) {
|
|
275
|
+
return 'outbound';
|
|
276
|
+
}
|
|
277
|
+
return message.agentName ? 'outbound' : 'unknown';
|
|
278
|
+
}
|
|
47
279
|
function normalizeUnreadChannel(channel) {
|
|
48
280
|
const channelName = readString(channel.channelName, channel.channel_name);
|
|
49
281
|
const unreadCount = readNumber(channel.unreadCount, channel.unread_count);
|
|
@@ -66,7 +298,7 @@ function normalizeMention(mention) {
|
|
|
66
298
|
id: mention.id,
|
|
67
299
|
channelName,
|
|
68
300
|
agentName,
|
|
69
|
-
text: mention.text,
|
|
301
|
+
text: readText(mention.text),
|
|
70
302
|
createdAt,
|
|
71
303
|
};
|
|
72
304
|
}
|
|
@@ -78,23 +310,32 @@ function normalizeLastMessage(message) {
|
|
|
78
310
|
if (!createdAt) {
|
|
79
311
|
return null;
|
|
80
312
|
}
|
|
313
|
+
const explicitUnread = readBoolean(message.unread, message.isUnread, message.is_unread);
|
|
314
|
+
const explicitRead = readBoolean(message.read, message.isRead, message.is_read);
|
|
81
315
|
return {
|
|
82
316
|
id: message.id,
|
|
83
|
-
text: message.text,
|
|
317
|
+
text: readText(message.text),
|
|
84
318
|
createdAt,
|
|
319
|
+
agentName: readString(message.agentName, message.agent_name),
|
|
320
|
+
unread: explicitUnread ?? (explicitRead === undefined ? undefined : !explicitRead),
|
|
85
321
|
};
|
|
86
322
|
}
|
|
87
323
|
function normalizeUnreadDm(dm) {
|
|
88
324
|
const conversationId = readString(dm.conversationId, dm.conversation_id);
|
|
89
|
-
const
|
|
90
|
-
|
|
325
|
+
const from = readString(dm.from);
|
|
326
|
+
const unreadCount = normalizeUnreadCount(readNumber(dm.unreadCount, dm.unread_count));
|
|
327
|
+
if (!conversationId || !from) {
|
|
91
328
|
return null;
|
|
92
329
|
}
|
|
93
330
|
return {
|
|
94
331
|
conversationId,
|
|
95
|
-
from
|
|
332
|
+
from,
|
|
96
333
|
unreadCount,
|
|
97
334
|
lastMessage: normalizeLastMessage(dm.lastMessage ?? dm.last_message),
|
|
335
|
+
// Current Relaycast inbox summaries expose last_message without sender
|
|
336
|
+
// metadata. Keep messages[] forward-compatible, but production previews
|
|
337
|
+
// are expected to use the explicit DM body fetch path today.
|
|
338
|
+
messages: (Array.isArray(dm.messages) ? dm.messages : []).map(normalizeLastMessage).filter(isPresent),
|
|
98
339
|
};
|
|
99
340
|
}
|
|
100
341
|
function normalizeRecentReaction(reaction) {
|
|
@@ -129,6 +370,86 @@ function normalizeInbox(inbox) {
|
|
|
129
370
|
.filter(isPresent),
|
|
130
371
|
};
|
|
131
372
|
}
|
|
373
|
+
async function getUnreadDmDisplayMessages(relaycast, dm) {
|
|
374
|
+
const embeddedMessages = dm.messages.length > 0 ? dm.messages : dm.lastMessage ? [dm.lastMessage] : [];
|
|
375
|
+
const inboundEmbedded = sortUnreadDmMessagesMostRecentFirst(embeddedMessages.filter((message) => isInboundUnreadDmMessage(message, dm.from)));
|
|
376
|
+
const unreadEmbedded = selectUnreadCandidates(inboundEmbedded, dm.unreadCount);
|
|
377
|
+
const targetVisibleCount = Math.min(3, dm.unreadCount);
|
|
378
|
+
if (unreadEmbedded.length >= targetVisibleCount || dm.unreadCount === 0) {
|
|
379
|
+
return unreadEmbedded;
|
|
380
|
+
}
|
|
381
|
+
try {
|
|
382
|
+
const fetchedMessages = (await relaycast.dms.messages(dm.conversationId, {
|
|
383
|
+
limit: INBOX_DM_PREVIEW_FETCH_LIMIT,
|
|
384
|
+
}))
|
|
385
|
+
.map((message) => normalizeDmMessage(message, {
|
|
386
|
+
createdAt: FALLBACK_DM_CREATED_AT,
|
|
387
|
+
}))
|
|
388
|
+
.filter(isPresent)
|
|
389
|
+
.filter((message) => message.agentName === dm.from);
|
|
390
|
+
const candidateMessages = selectUnreadCandidates(fetchedMessages, dm.unreadCount);
|
|
391
|
+
const mergedMessages = new Map();
|
|
392
|
+
for (const message of [...unreadEmbedded, ...candidateMessages]) {
|
|
393
|
+
mergedMessages.set(message.id, message);
|
|
394
|
+
}
|
|
395
|
+
return sortUnreadDmMessagesMostRecentFirst([...mergedMessages.values()]);
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
if (unreadEmbedded.length === 0 && dm.unreadCount > 0) {
|
|
399
|
+
return [
|
|
400
|
+
{
|
|
401
|
+
id: `diagnostic:${dm.conversationId}`,
|
|
402
|
+
text: '',
|
|
403
|
+
createdAt: FALLBACK_DM_CREATED_AT,
|
|
404
|
+
diagnostic: `(could not load message bodies — run \`agent-relay replies ${renderShellQuotedForTerminal(dm.from)} --unread\`)`,
|
|
405
|
+
},
|
|
406
|
+
];
|
|
407
|
+
}
|
|
408
|
+
return unreadEmbedded;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
412
|
+
const step = Math.max(1, Math.floor(concurrency));
|
|
413
|
+
const results = new Array(items.length);
|
|
414
|
+
for (let index = 0; index < items.length; index += step) {
|
|
415
|
+
const batch = items.slice(index, index + step);
|
|
416
|
+
const batchResults = await Promise.all(batch.map((item, batchIndex) => mapper(item, index + batchIndex)));
|
|
417
|
+
for (const [batchIndex, result] of batchResults.entries()) {
|
|
418
|
+
results[index + batchIndex] = result;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return results;
|
|
422
|
+
}
|
|
423
|
+
async function markDmMessagesRead(relaycast, conversationId, messages) {
|
|
424
|
+
const ids = messages.map((message) => message.id);
|
|
425
|
+
if (ids.length === 0) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (typeof relaycast.markRead === 'function') {
|
|
429
|
+
await mapWithConcurrency(ids, MARK_READ_CONCURRENCY, async (id) => relaycast.markRead(id));
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (typeof relaycast.dms.markMessagesRead === 'function') {
|
|
433
|
+
await relaycast.dms.markMessagesRead(conversationId, ids);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (typeof relaycast.dms.markRead === 'function') {
|
|
437
|
+
await relaycast.dms.markRead(conversationId, ids);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (typeof relaycast.markMessagesRead === 'function') {
|
|
441
|
+
await relaycast.markMessagesRead(conversationId, ids);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function getConversationRecency(conversation) {
|
|
445
|
+
const lastMessage = conversation.lastMessage ?? conversation.last_message;
|
|
446
|
+
return (Date.parse(readString(lastMessage?.createdAt, lastMessage?.created_at) ?? '') ||
|
|
447
|
+
Date.parse(readString(conversation.createdAt, conversation.created_at) ?? '') ||
|
|
448
|
+
0);
|
|
449
|
+
}
|
|
450
|
+
function sortConversationsMostRecentFirst(conversations) {
|
|
451
|
+
return [...conversations].sort((a, b) => getConversationRecency(b) - getConversationRecency(a));
|
|
452
|
+
}
|
|
132
453
|
async function createDefaultClient(cwd) {
|
|
133
454
|
// Connect to an existing broker if one is running, otherwise spawn
|
|
134
455
|
try {
|
|
@@ -177,10 +498,14 @@ async function createDefaultRelaycastClient(options) {
|
|
|
177
498
|
type: 'agent',
|
|
178
499
|
});
|
|
179
500
|
const agentClient = relaycast.as(registration.token);
|
|
501
|
+
return wrapRelaycastAgentClient(agentClient);
|
|
502
|
+
}
|
|
503
|
+
export function wrapRelaycastAgentClient(agentClient) {
|
|
180
504
|
// AgentClient already has dm(agent, text) — preserve the original reference before casting.
|
|
181
505
|
// post() bridges to AgentClient.send() which has a different name.
|
|
182
506
|
const originalDm = agentClient.dm.bind(agentClient);
|
|
183
507
|
const originalSend = agentClient.send.bind(agentClient);
|
|
508
|
+
const originalDisconnect = agentClient.disconnect.bind(agentClient);
|
|
184
509
|
const client = agentClient;
|
|
185
510
|
client.dm = async (to, text) => {
|
|
186
511
|
await originalDm(to, text);
|
|
@@ -188,6 +513,9 @@ async function createDefaultRelaycastClient(options) {
|
|
|
188
513
|
client.post = async (channel, text) => {
|
|
189
514
|
await originalSend(channel, text);
|
|
190
515
|
};
|
|
516
|
+
client.disconnect = async () => {
|
|
517
|
+
await originalDisconnect();
|
|
518
|
+
};
|
|
191
519
|
return client;
|
|
192
520
|
}
|
|
193
521
|
function withDefaults(overrides = {}) {
|
|
@@ -208,16 +536,17 @@ export function registerMessagingCommands(program, overrides = {}) {
|
|
|
208
536
|
.description('Send a message to an agent')
|
|
209
537
|
.argument('<agent>', 'Target agent name (or * for broadcast, #channel for channel)')
|
|
210
538
|
.argument('<message>', 'Message to send')
|
|
211
|
-
.option('--from <name>', 'Sender name (registered identity in relaycast
|
|
539
|
+
.option('--from <name>', 'Sender name (registered identity in relaycast). Default: $AGENT_RELAY_ORCHESTRATOR_NAME or "orchestrator"; use this identity with `agent-relay replies <worker>`.')
|
|
212
540
|
.option('--thread <id>', 'Thread identifier')
|
|
213
541
|
.action(async (agent, message, options) => {
|
|
214
|
-
const senderName = options.from?.trim() ||
|
|
542
|
+
const senderName = options.from?.trim() || getDefaultOrchestratorName();
|
|
215
543
|
const isChannel = agent.startsWith('#');
|
|
216
544
|
// Primary path: send via relaycast SDK so messages are stored and queryable
|
|
217
545
|
// Skip relaycast path when --thread is used since the relaycast SDK does not support threading
|
|
218
546
|
if (!options.thread) {
|
|
547
|
+
let relaycastClient;
|
|
219
548
|
try {
|
|
220
|
-
|
|
549
|
+
relaycastClient = await deps.createRelaycastClient({
|
|
221
550
|
agentName: senderName,
|
|
222
551
|
cwd: deps.getProjectRoot(),
|
|
223
552
|
});
|
|
@@ -227,12 +556,15 @@ export function registerMessagingCommands(program, overrides = {}) {
|
|
|
227
556
|
else {
|
|
228
557
|
await relaycastClient.dm(agent, message);
|
|
229
558
|
}
|
|
230
|
-
deps.log(`Message sent to ${agent}`);
|
|
559
|
+
deps.log(`Message sent to ${sanitizeForTerminalLine(agent)}`);
|
|
231
560
|
return;
|
|
232
561
|
}
|
|
233
562
|
catch {
|
|
234
563
|
// Fall through to broker path
|
|
235
564
|
}
|
|
565
|
+
finally {
|
|
566
|
+
await disconnectRelaycastClient(relaycastClient);
|
|
567
|
+
}
|
|
236
568
|
}
|
|
237
569
|
// Fallback: broker path (for environments without relaycast API key)
|
|
238
570
|
let brokerClient;
|
|
@@ -240,7 +572,7 @@ export function registerMessagingCommands(program, overrides = {}) {
|
|
|
240
572
|
brokerClient = await deps.createClient(deps.getProjectRoot());
|
|
241
573
|
}
|
|
242
574
|
catch (err) {
|
|
243
|
-
deps.error(`Failed to connect to broker: ${
|
|
575
|
+
deps.error(`Failed to connect to broker: ${formatErrorDetail(err)}`);
|
|
244
576
|
deps.error('Start the broker with `agent-relay up` and try again.');
|
|
245
577
|
deps.exit(1);
|
|
246
578
|
return;
|
|
@@ -249,13 +581,13 @@ export function registerMessagingCommands(program, overrides = {}) {
|
|
|
249
581
|
await brokerClient.sendMessage({
|
|
250
582
|
to: agent,
|
|
251
583
|
text: message,
|
|
252
|
-
from:
|
|
584
|
+
from: senderName,
|
|
253
585
|
threadId: options.thread,
|
|
254
586
|
});
|
|
255
|
-
deps.log(`Message sent to ${agent}`);
|
|
587
|
+
deps.log(`Message sent to ${sanitizeForTerminalLine(agent)}`);
|
|
256
588
|
}
|
|
257
589
|
catch (err) {
|
|
258
|
-
deps.error(`Failed to send message: ${
|
|
590
|
+
deps.error(`Failed to send message: ${formatErrorDetail(err)}`);
|
|
259
591
|
deps.exit(1);
|
|
260
592
|
}
|
|
261
593
|
finally {
|
|
@@ -276,7 +608,7 @@ export function registerMessagingCommands(program, overrides = {}) {
|
|
|
276
608
|
});
|
|
277
609
|
}
|
|
278
610
|
catch (err) {
|
|
279
|
-
deps.error(`Failed to initialize relaycast client: ${
|
|
611
|
+
deps.error(`Failed to initialize relaycast client: ${formatErrorDetail(err)}`);
|
|
280
612
|
deps.exit(1);
|
|
281
613
|
return;
|
|
282
614
|
}
|
|
@@ -286,17 +618,20 @@ export function registerMessagingCommands(program, overrides = {}) {
|
|
|
286
618
|
if (!normalizedMessage) {
|
|
287
619
|
throw new Error(`message ${messageId} is missing sender or timestamp metadata`);
|
|
288
620
|
}
|
|
289
|
-
deps.log(`From: ${normalizedMessage.agentName}`);
|
|
621
|
+
deps.log(`From: ${sanitizeForTerminalLine(normalizedMessage.agentName)}`);
|
|
290
622
|
deps.log('To: #channel');
|
|
291
|
-
deps.log(`Time: ${normalizedMessage.createdAt}`);
|
|
623
|
+
deps.log(`Time: ${sanitizeForTerminalLine(normalizedMessage.createdAt)}`);
|
|
292
624
|
deps.log('---');
|
|
293
625
|
deps.log(normalizedMessage.text);
|
|
294
626
|
}
|
|
295
627
|
catch (err) {
|
|
296
|
-
deps.error(`Failed to read message ${messageId}: ${
|
|
628
|
+
deps.error(`Failed to read message ${sanitizeForTerminalLine(messageId)}: ${formatErrorDetail(err)}`);
|
|
297
629
|
deps.error('Ensure the broker is running (`agent-relay up`) and try again.');
|
|
298
630
|
deps.exit(1);
|
|
299
631
|
}
|
|
632
|
+
finally {
|
|
633
|
+
await disconnectRelaycastClient(relaycast);
|
|
634
|
+
}
|
|
300
635
|
});
|
|
301
636
|
program
|
|
302
637
|
.command('history')
|
|
@@ -305,67 +640,124 @@ export function registerMessagingCommands(program, overrides = {}) {
|
|
|
305
640
|
.option('-f, --from <agent>', 'Filter by sender')
|
|
306
641
|
.option('-t, --to <agent>', 'Filter by recipient')
|
|
307
642
|
.option('--thread <id>', 'Filter by thread ID')
|
|
308
|
-
.option('--since <time>', '
|
|
643
|
+
.option('--since <time|id>', 'Time ("1h", "30m", "2024-01-01") or a message-id cursor (only messages after that id)')
|
|
309
644
|
.option('--json', 'Output as JSON')
|
|
310
645
|
.option('--storage <type>', 'Storage type override (jsonl, sqlite, memory)')
|
|
311
646
|
.action(async (options) => {
|
|
312
|
-
|
|
313
|
-
|
|
647
|
+
let limit;
|
|
648
|
+
let since;
|
|
649
|
+
try {
|
|
650
|
+
limit = parseMessageLimit(options.limit);
|
|
651
|
+
since = resolveSince(options.since);
|
|
652
|
+
}
|
|
653
|
+
catch (err) {
|
|
654
|
+
deps.error(formatErrorDetail(err));
|
|
655
|
+
deps.exit(1);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const sinceTs = since.kind === 'time' ? since.ts : undefined;
|
|
314
659
|
if (options.from && !options.to) {
|
|
660
|
+
if (!isAuthorizedReadIdentity(options.from)) {
|
|
661
|
+
reportUnauthorizedReadIdentity(deps, options.from);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
315
664
|
// Cross-context sender history: channel messages + DMs sent by this agent
|
|
316
665
|
const channelItems = [];
|
|
317
666
|
const dmItems = [];
|
|
318
|
-
|
|
667
|
+
const sourceErrors = [];
|
|
668
|
+
let relaycastClient;
|
|
319
669
|
try {
|
|
320
|
-
|
|
321
|
-
agentName:
|
|
670
|
+
relaycastClient = await deps.createRelaycastClient({
|
|
671
|
+
agentName: options.from,
|
|
322
672
|
cwd: deps.getProjectRoot(),
|
|
323
673
|
});
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
674
|
+
}
|
|
675
|
+
catch (err) {
|
|
676
|
+
const detail = formatErrorDetail(err);
|
|
677
|
+
sourceErrors.push(`channel: ${detail}`);
|
|
678
|
+
sourceErrors.push(`dm: ${detail}`);
|
|
679
|
+
}
|
|
680
|
+
// Part 1: channel messages from this agent
|
|
681
|
+
try {
|
|
682
|
+
if (!relaycastClient) {
|
|
683
|
+
// Initialization failure already contributed both source errors.
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
const raw = (await relaycastClient.messages('general', { limit: Math.max(limit * 2, 100) }))
|
|
687
|
+
.map(normalizeMessage)
|
|
688
|
+
.filter(isPresent)
|
|
689
|
+
.filter((msg) => msg.agentName === options.from)
|
|
690
|
+
.filter((msg) => !sinceTs || Date.parse(msg.createdAt) >= sinceTs)
|
|
691
|
+
// Relaycast feed order is not guaranteed: sort chronologically
|
|
692
|
+
// and keep the most recent `limit` (matches the channel branch).
|
|
693
|
+
// A bare slice(0, limit) here silently kept the OLDEST messages.
|
|
694
|
+
.sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt))
|
|
695
|
+
.slice(-limit);
|
|
696
|
+
for (const msg of raw) {
|
|
697
|
+
channelItems.push({
|
|
698
|
+
id: msg.id,
|
|
699
|
+
ts: msg.createdAt,
|
|
700
|
+
to: '#general',
|
|
701
|
+
text: msg.text,
|
|
702
|
+
kind: 'channel',
|
|
703
|
+
});
|
|
704
|
+
}
|
|
332
705
|
}
|
|
333
706
|
}
|
|
334
|
-
catch {
|
|
335
|
-
|
|
707
|
+
catch (err) {
|
|
708
|
+
sourceErrors.push(`channel: ${formatErrorDetail(err)}`);
|
|
336
709
|
}
|
|
337
710
|
// Part 2: DM messages sent by this agent
|
|
338
711
|
try {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
712
|
+
if (!relaycastClient) {
|
|
713
|
+
// Initialization failure already contributed both source errors.
|
|
714
|
+
}
|
|
715
|
+
else {
|
|
716
|
+
const conversations = sortConversationsMostRecentFirst(await relaycastClient.dms.conversations()).slice(0, HISTORY_DM_CONVERSATION_SCAN_LIMIT);
|
|
717
|
+
const perConvLimit = Math.min(HISTORY_DM_PER_CONVERSATION_FETCH_LIMIT, MAX_DM_FETCH_LIMIT, Math.max(limit, 10));
|
|
718
|
+
const perConversationItems = await mapWithConcurrency(conversations, MARK_READ_CONCURRENCY, async (conv) => {
|
|
719
|
+
const msgs = await relaycastClient.dms.messages(conv.id, { limit: perConvLimit });
|
|
720
|
+
const recipient = conv.participants
|
|
721
|
+
.filter((p) => (p.agentName || p.agent_name) !== options.from)
|
|
722
|
+
.map((p) => p.agentName || p.agent_name)
|
|
723
|
+
.join(', ') || '(self)';
|
|
724
|
+
return msgs
|
|
725
|
+
.map((m) => {
|
|
726
|
+
const sender = m.agentName || m.agent_name;
|
|
727
|
+
if (sender !== options.from)
|
|
728
|
+
return null;
|
|
729
|
+
const ts = m.createdAt || m.created_at || '';
|
|
730
|
+
if (sinceTs && Date.parse(ts) < sinceTs)
|
|
731
|
+
return null;
|
|
732
|
+
return { id: m.id, ts, to: recipient, text: readText(m.text), kind: 'dm' };
|
|
733
|
+
})
|
|
734
|
+
.filter(isPresent);
|
|
735
|
+
});
|
|
736
|
+
for (const items of perConversationItems) {
|
|
737
|
+
dmItems.push(...items);
|
|
359
738
|
}
|
|
360
739
|
}
|
|
361
740
|
}
|
|
362
|
-
catch {
|
|
363
|
-
|
|
741
|
+
catch (err) {
|
|
742
|
+
sourceErrors.push(`dm: ${formatErrorDetail(err)}`);
|
|
743
|
+
}
|
|
744
|
+
finally {
|
|
745
|
+
await disconnectRelaycastClient(relaycastClient);
|
|
746
|
+
}
|
|
747
|
+
const orderedItems = [...channelItems, ...dmItems].sort((a, b) => Date.parse(a.ts) - Date.parse(b.ts));
|
|
748
|
+
const allItems = (since.kind === 'cursor' ? sliceAfterCursor(orderedItems, since.id) : orderedItems).slice(-limit);
|
|
749
|
+
if (!allItems.length && sourceErrors.length >= 2) {
|
|
750
|
+
deps.error(`Failed to fetch history sources: ${sourceErrors.join('; ')}`);
|
|
751
|
+
deps.exit(1);
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (sourceErrors.length > 0) {
|
|
755
|
+
deps.error(`Warning: partial history results; ${sourceErrors.join('; ')}`);
|
|
364
756
|
}
|
|
365
|
-
const allItems = [...channelItems, ...dmItems].sort((a, b) => Date.parse(a.ts) - Date.parse(b.ts));
|
|
366
757
|
if (options.json) {
|
|
367
758
|
deps.log(JSON.stringify(allItems.map((item) => ({
|
|
368
759
|
from: options.from,
|
|
760
|
+
id: item.id,
|
|
369
761
|
to: item.to,
|
|
370
762
|
text: item.text,
|
|
371
763
|
createdAt: item.ts,
|
|
@@ -378,87 +770,94 @@ export function registerMessagingCommands(program, overrides = {}) {
|
|
|
378
770
|
return;
|
|
379
771
|
}
|
|
380
772
|
allItems.forEach((item) => {
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
773
|
+
const suffix = item.kind === 'dm' ? ' (DM)' : '';
|
|
774
|
+
const header = `[${sanitizeForTerminalLine(item.ts)}] ${sanitizeForTerminalLine(options.from ?? '')} -> ${sanitizeForTerminalLine(item.to)}${suffix}`;
|
|
775
|
+
// No truncation: substantive payloads (diffs, grep counts,
|
|
776
|
+
// GO/NO-GO reasoning) must be readable in full. Multi-line
|
|
777
|
+
// messages print under an indented header.
|
|
778
|
+
const lines = item.text.split(/\r?\n/).map(sanitizeForTerminal);
|
|
779
|
+
if (lines.length === 1) {
|
|
780
|
+
deps.log(`${header}: ${lines[0]}`);
|
|
384
781
|
}
|
|
385
782
|
else {
|
|
386
|
-
deps.log(
|
|
783
|
+
deps.log(`${header}:`);
|
|
784
|
+
for (const line of lines) {
|
|
785
|
+
deps.log(` ${line}`);
|
|
786
|
+
}
|
|
387
787
|
}
|
|
388
788
|
});
|
|
389
789
|
return;
|
|
390
790
|
}
|
|
391
791
|
if (options.to && !options.to.startsWith('#')) {
|
|
392
|
-
|
|
792
|
+
const toName = options.to;
|
|
793
|
+
const readerName = getDefaultOrchestratorName();
|
|
393
794
|
let dmClient;
|
|
394
795
|
try {
|
|
395
796
|
dmClient = await deps.createRelaycastClient({
|
|
396
|
-
agentName:
|
|
797
|
+
agentName: readerName,
|
|
397
798
|
cwd: deps.getProjectRoot(),
|
|
398
799
|
});
|
|
399
800
|
}
|
|
400
801
|
catch (err) {
|
|
401
|
-
deps.error(`Failed to initialize relaycast client: ${
|
|
802
|
+
deps.error(`Failed to initialize relaycast client: ${formatErrorDetail(err)}`);
|
|
402
803
|
deps.exit(1);
|
|
403
804
|
return;
|
|
404
805
|
}
|
|
405
806
|
try {
|
|
406
807
|
const conversations = await dmClient.dms.conversations();
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
deps.log(`No DM conversation found between ${options.to} and ${options.from}.`);
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
const messages = await dmClient.dms.messages(conv.id, { limit });
|
|
415
|
-
if (options.json) {
|
|
416
|
-
deps.log(JSON.stringify(messages.map((m) => ({
|
|
417
|
-
id: m.id,
|
|
418
|
-
from: m.agentName || m.agent_name || 'unknown',
|
|
419
|
-
text: m.text,
|
|
420
|
-
createdAt: m.createdAt || m.created_at,
|
|
421
|
-
})), null, 2));
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
if (!messages.length) {
|
|
425
|
-
deps.log('No messages found.');
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
messages.forEach((m) => {
|
|
429
|
-
const sender = m.agentName || m.agent_name || 'unknown';
|
|
430
|
-
const ts = m.createdAt || m.created_at || '';
|
|
431
|
-
const body = m.text.length > 200 ? `${m.text.slice(0, 197)}...` : m.text;
|
|
432
|
-
deps.log(`[${ts}] ${sender}: ${body}`);
|
|
433
|
-
});
|
|
808
|
+
const conversation = findDirectDmConversation(conversations, readerName, toName);
|
|
809
|
+
if (!conversation) {
|
|
810
|
+
deps.log(`No DM conversation found between ${sanitizeForTerminalLine(readerName)} and ${sanitizeForTerminalLine(toName)}.`);
|
|
811
|
+
return;
|
|
434
812
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const preview = lastText.length > 60 ? `${lastText.slice(0, 57)}...` : lastText;
|
|
453
|
-
const unread = conv.unreadCount ?? conv.unread_count ?? 0;
|
|
454
|
-
deps.log(` ${others || '(self)'}: "${preview}" [${unread} unread]`);
|
|
813
|
+
const collected = [];
|
|
814
|
+
const rawFetchLimit = options.from || sinceTs || since.kind === 'cursor' ? getFilteredDmFetchLimit(limit) : limit;
|
|
815
|
+
const dmMessages = await dmClient.dms.messages(conversation.id, { limit: rawFetchLimit });
|
|
816
|
+
for (const message of dmMessages) {
|
|
817
|
+
const normalized = normalizeDmMessage(message, {
|
|
818
|
+
agentName: toName,
|
|
819
|
+
createdAt: FALLBACK_DM_CREATED_AT,
|
|
820
|
+
});
|
|
821
|
+
if (!normalized)
|
|
822
|
+
continue;
|
|
823
|
+
if (options.from && normalized.agentName !== options.from)
|
|
824
|
+
continue;
|
|
825
|
+
if (sinceTs && Date.parse(normalized.createdAt) < sinceTs)
|
|
826
|
+
continue;
|
|
827
|
+
collected.push({
|
|
828
|
+
...normalized,
|
|
829
|
+
to: normalized.agentName === readerName ? toName : readerName,
|
|
455
830
|
});
|
|
456
831
|
}
|
|
832
|
+
const ordered = sortDmMessagesChronologically(collected);
|
|
833
|
+
// `--since <id>`: only messages strictly after that id (no replay).
|
|
834
|
+
const messages = (since.kind === 'cursor' ? sliceAfterCursor(ordered, since.id) : ordered).slice(-limit);
|
|
835
|
+
if (options.json) {
|
|
836
|
+
deps.log(JSON.stringify(messages.map((message) => ({
|
|
837
|
+
id: message.id,
|
|
838
|
+
from: message.agentName,
|
|
839
|
+
to: message.to,
|
|
840
|
+
text: message.text,
|
|
841
|
+
createdAt: message.createdAt,
|
|
842
|
+
direction: message.agentName === readerName ? 'outbound' : 'inbound',
|
|
843
|
+
})), null, 2));
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (!messages.length) {
|
|
847
|
+
deps.log('No messages found.');
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
for (const message of messages) {
|
|
851
|
+
renderTranscriptMessage(deps.log, message);
|
|
852
|
+
}
|
|
457
853
|
}
|
|
458
854
|
catch (err) {
|
|
459
|
-
deps.error(`Failed to fetch DM history: ${
|
|
855
|
+
deps.error(`Failed to fetch DM history: ${formatErrorDetail(err)}`);
|
|
460
856
|
deps.exit(1);
|
|
461
857
|
}
|
|
858
|
+
finally {
|
|
859
|
+
await disconnectRelaycastClient(dmClient);
|
|
860
|
+
}
|
|
462
861
|
return;
|
|
463
862
|
}
|
|
464
863
|
let relaycast;
|
|
@@ -469,7 +868,7 @@ export function registerMessagingCommands(program, overrides = {}) {
|
|
|
469
868
|
});
|
|
470
869
|
}
|
|
471
870
|
catch (err) {
|
|
472
|
-
deps.error(`Failed to initialize relaycast client: ${
|
|
871
|
+
deps.error(`Failed to initialize relaycast client: ${formatErrorDetail(err)}`);
|
|
473
872
|
deps.exit(1);
|
|
474
873
|
return;
|
|
475
874
|
}
|
|
@@ -488,7 +887,17 @@ export function registerMessagingCommands(program, overrides = {}) {
|
|
|
488
887
|
return false;
|
|
489
888
|
return true;
|
|
490
889
|
});
|
|
491
|
-
|
|
890
|
+
// Order chronologically (oldest first, newest at the bottom) so a
|
|
891
|
+
// reader reconstructs the conversation top-to-bottom like a
|
|
892
|
+
// transcript, then keep the most recent `limit` messages. The
|
|
893
|
+
// relaycast feed order is not guaranteed, so an explicit sort is
|
|
894
|
+
// required to stop messages interleaving out of order.
|
|
895
|
+
filteredMessages = filteredMessages.sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
|
|
896
|
+
// `--since <id>`: only messages strictly after that id (no replay).
|
|
897
|
+
if (since.kind === 'cursor') {
|
|
898
|
+
filteredMessages = sliceAfterCursor(filteredMessages, since.id);
|
|
899
|
+
}
|
|
900
|
+
filteredMessages = filteredMessages.slice(-limit);
|
|
492
901
|
if (options.json) {
|
|
493
902
|
const payload = filteredMessages.map((msg) => ({
|
|
494
903
|
id: msg.id,
|
|
@@ -508,16 +917,31 @@ export function registerMessagingCommands(program, overrides = {}) {
|
|
|
508
917
|
deps.log('No messages found.');
|
|
509
918
|
return;
|
|
510
919
|
}
|
|
920
|
+
// No truncation: channel evidence (literal diffs, grep counts,
|
|
921
|
+
// GO/NO-GO reasoning) must be fully readable. Multi-line messages
|
|
922
|
+
// print under an indented header.
|
|
511
923
|
filteredMessages.forEach((msg) => {
|
|
512
|
-
const
|
|
513
|
-
|
|
924
|
+
const header = `[${sanitizeForTerminalLine(msg.createdAt)}] ${sanitizeForTerminalLine(msg.agentName)} -> #${sanitizeForTerminalLine(channel)}`;
|
|
925
|
+
const lines = msg.text.split(/\r?\n/).map(sanitizeForTerminal);
|
|
926
|
+
if (lines.length === 1) {
|
|
927
|
+
deps.log(`${header}: ${lines[0]}`);
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
deps.log(`${header}:`);
|
|
931
|
+
for (const line of lines) {
|
|
932
|
+
deps.log(` ${line}`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
514
935
|
});
|
|
515
936
|
}
|
|
516
937
|
catch (err) {
|
|
517
|
-
deps.error(`Failed to fetch history: ${
|
|
938
|
+
deps.error(`Failed to fetch history: ${formatErrorDetail(err)}`);
|
|
518
939
|
deps.error('Ensure the broker is running (`agent-relay up`) and try again.');
|
|
519
940
|
deps.exit(1);
|
|
520
941
|
}
|
|
942
|
+
finally {
|
|
943
|
+
await disconnectRelaycastClient(relaycast);
|
|
944
|
+
}
|
|
521
945
|
});
|
|
522
946
|
program
|
|
523
947
|
.command('inbox')
|
|
@@ -525,21 +949,29 @@ export function registerMessagingCommands(program, overrides = {}) {
|
|
|
525
949
|
.option('--agent <name>', 'Agent whose inbox to check (defaults to cli user)')
|
|
526
950
|
.option('--json', 'Output as JSON')
|
|
527
951
|
.action(async (options) => {
|
|
952
|
+
const requestedReaderName = options.agent?.trim();
|
|
953
|
+
if (requestedReaderName && !isAuthorizedReadIdentity(requestedReaderName)) {
|
|
954
|
+
reportUnauthorizedReadIdentity(deps, requestedReaderName);
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
const readerName = requestedReaderName || '__cli_inbox__';
|
|
528
958
|
let relaycast;
|
|
529
959
|
try {
|
|
530
960
|
relaycast = await deps.createRelaycastClient({
|
|
531
|
-
agentName:
|
|
961
|
+
agentName: readerName,
|
|
532
962
|
cwd: deps.getProjectRoot(),
|
|
533
963
|
});
|
|
534
964
|
}
|
|
535
965
|
catch (err) {
|
|
536
|
-
deps.error(`Failed to initialize relaycast client: ${
|
|
966
|
+
deps.error(`Failed to initialize relaycast client: ${formatErrorDetail(err)}`);
|
|
537
967
|
deps.exit(1);
|
|
538
968
|
return;
|
|
539
969
|
}
|
|
540
970
|
try {
|
|
541
971
|
const inbox = normalizeInbox(await relaycast.inbox());
|
|
542
972
|
if (options.json) {
|
|
973
|
+
const unreadDmsForFetch = inbox.unreadDms.slice(0, INBOX_UNREAD_DM_FETCH_CONVERSATION_LIMIT);
|
|
974
|
+
const unreadDmDisplays = await mapWithConcurrency(unreadDmsForFetch, MARK_READ_CONCURRENCY, async (dm) => getUnreadDmDisplayMessages(relaycast, dm));
|
|
543
975
|
const payload = {
|
|
544
976
|
unread_channels: inbox.unreadChannels.map((item) => ({
|
|
545
977
|
channel_name: item.channelName,
|
|
@@ -552,18 +984,29 @@ export function registerMessagingCommands(program, overrides = {}) {
|
|
|
552
984
|
text: mention.text,
|
|
553
985
|
created_at: mention.createdAt,
|
|
554
986
|
})),
|
|
555
|
-
unread_dms: inbox.unreadDms.map((dm) =>
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
987
|
+
unread_dms: inbox.unreadDms.map((dm, index) => {
|
|
988
|
+
const selectedMessage = unreadDmDisplays[index]?.[0];
|
|
989
|
+
const lastMessageError = selectedMessage?.diagnostic;
|
|
990
|
+
const selectedDisplayMessage = lastMessageError ? undefined : selectedMessage;
|
|
991
|
+
const lastMessage = selectedDisplayMessage ?? dm.lastMessage;
|
|
992
|
+
return {
|
|
993
|
+
conversation_id: dm.conversationId,
|
|
994
|
+
from: dm.from,
|
|
995
|
+
unread_count: dm.unreadCount,
|
|
996
|
+
last_message: lastMessage
|
|
997
|
+
? {
|
|
998
|
+
id: lastMessage.id,
|
|
999
|
+
text: lastMessage.text,
|
|
1000
|
+
created_at: lastMessage.createdAt,
|
|
1001
|
+
direction: selectedDisplayMessage?.direction ??
|
|
1002
|
+
(selectedDisplayMessage
|
|
1003
|
+
? 'inbound'
|
|
1004
|
+
: getLastMessageDirection(lastMessage, dm.from, readerName)),
|
|
1005
|
+
}
|
|
1006
|
+
: null,
|
|
1007
|
+
...(lastMessageError ? { last_message_error: lastMessageError } : {}),
|
|
1008
|
+
};
|
|
1009
|
+
}),
|
|
567
1010
|
recent_reactions: inbox.recentReactions.map((reaction) => ({
|
|
568
1011
|
message_id: reaction.messageId,
|
|
569
1012
|
channel_name: reaction.channelName,
|
|
@@ -586,7 +1029,7 @@ export function registerMessagingCommands(program, overrides = {}) {
|
|
|
586
1029
|
if (inbox.unreadChannels.length > 0) {
|
|
587
1030
|
deps.log('Unread Channels:');
|
|
588
1031
|
for (const item of inbox.unreadChannels) {
|
|
589
|
-
deps.log(` #${item.channelName}: ${item.unreadCount}`);
|
|
1032
|
+
deps.log(` #${sanitizeForTerminalLine(item.channelName)}: ${item.unreadCount}`);
|
|
590
1033
|
}
|
|
591
1034
|
deps.log('');
|
|
592
1035
|
}
|
|
@@ -594,28 +1037,149 @@ export function registerMessagingCommands(program, overrides = {}) {
|
|
|
594
1037
|
deps.log('Mentions:');
|
|
595
1038
|
for (const mention of inbox.mentions) {
|
|
596
1039
|
const preview = mention.text.length > 120 ? `${mention.text.slice(0, 117)}...` : mention.text;
|
|
597
|
-
deps.log(` [${mention.createdAt}] #${mention.channelName} @${mention.agentName}: ${preview}`);
|
|
1040
|
+
deps.log(` [${sanitizeForTerminalLine(mention.createdAt)}] #${sanitizeForTerminalLine(mention.channelName)} @${sanitizeForTerminalLine(mention.agentName)}: ${sanitizeForTerminalLine(preview)}`);
|
|
598
1041
|
}
|
|
599
1042
|
deps.log('');
|
|
600
1043
|
}
|
|
601
1044
|
if (inbox.unreadDms.length > 0) {
|
|
602
1045
|
deps.log('Unread DMs:');
|
|
603
|
-
|
|
604
|
-
|
|
1046
|
+
const unreadDmsForFetch = inbox.unreadDms.slice(0, INBOX_UNREAD_DM_FETCH_CONVERSATION_LIMIT);
|
|
1047
|
+
const unreadDmDisplays = await mapWithConcurrency(unreadDmsForFetch, MARK_READ_CONCURRENCY, async (dm) => getUnreadDmDisplayMessages(relaycast, dm));
|
|
1048
|
+
for (const [index, dm] of inbox.unreadDms.entries()) {
|
|
1049
|
+
deps.log(` ${sanitizeForTerminalLine(dm.from)} → ${sanitizeForTerminalLine(readerName)} (${dm.unreadCount} unread):`);
|
|
1050
|
+
const displayMessages = unreadDmDisplays[index] ?? [];
|
|
1051
|
+
const visibleMessages = displayMessages.slice(0, 3);
|
|
1052
|
+
for (const message of visibleMessages) {
|
|
1053
|
+
renderUnreadDmMessage(deps.log, message, dm.from);
|
|
1054
|
+
}
|
|
1055
|
+
const remaining = Math.max(dm.unreadCount, displayMessages.length) - visibleMessages.length;
|
|
1056
|
+
if (remaining > 0) {
|
|
1057
|
+
deps.log(` … (${remaining} more — run \`agent-relay replies ${renderShellQuotedForTerminal(dm.from)} --unread\` to see all)`);
|
|
1058
|
+
}
|
|
605
1059
|
}
|
|
606
1060
|
deps.log('');
|
|
607
1061
|
}
|
|
608
1062
|
if (inbox.recentReactions.length > 0) {
|
|
609
1063
|
deps.log('Recent Reactions:');
|
|
610
1064
|
for (const reaction of inbox.recentReactions) {
|
|
611
|
-
deps.log(` [${reaction.createdAt}] #${reaction.channelName} ${reaction.emoji} by @${reaction.agentName}`);
|
|
1065
|
+
deps.log(` [${sanitizeForTerminalLine(reaction.createdAt)}] #${sanitizeForTerminalLine(reaction.channelName)} ${sanitizeForTerminalLine(reaction.emoji)} by @${sanitizeForTerminalLine(reaction.agentName)}`);
|
|
612
1066
|
}
|
|
613
1067
|
}
|
|
614
1068
|
}
|
|
615
1069
|
catch (err) {
|
|
616
|
-
deps.error(`Failed to fetch inbox: ${
|
|
1070
|
+
deps.error(`Failed to fetch inbox: ${formatErrorDetail(err)}`);
|
|
617
1071
|
deps.exit(1);
|
|
618
1072
|
}
|
|
1073
|
+
finally {
|
|
1074
|
+
await disconnectRelaycastClient(relaycast);
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
program
|
|
1078
|
+
.command('replies')
|
|
1079
|
+
.description('Show DM replies received from an agent')
|
|
1080
|
+
.argument('<agent>', 'Agent whose replies to show')
|
|
1081
|
+
.option('-n, --limit <count>', 'Number of messages to show', '50')
|
|
1082
|
+
.option('--since <time|id>', 'Time ("5m", "1h", ISO-8601) or a message-id cursor (only messages after that id; no replay)')
|
|
1083
|
+
.option('--unread', 'Only unread messages')
|
|
1084
|
+
.option('--mark-read', 'Mark displayed unread replies as read')
|
|
1085
|
+
.option('--as <name>', 'Read as this orchestrator identity')
|
|
1086
|
+
.option('--json', 'Output as JSON')
|
|
1087
|
+
.option('--full', 'Disable truncation; text is always printed in full')
|
|
1088
|
+
.action(async (agent, options) => {
|
|
1089
|
+
let limit;
|
|
1090
|
+
let since;
|
|
1091
|
+
try {
|
|
1092
|
+
limit = parseMessageLimit(options.limit);
|
|
1093
|
+
since = resolveSince(options.since);
|
|
1094
|
+
}
|
|
1095
|
+
catch (err) {
|
|
1096
|
+
deps.error(formatErrorDetail(err));
|
|
1097
|
+
deps.exit(1);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
const sinceTs = since.kind === 'time' ? since.ts : undefined;
|
|
1101
|
+
const requestedReaderName = options.as?.trim();
|
|
1102
|
+
if (requestedReaderName && !isAuthorizedReadIdentity(requestedReaderName)) {
|
|
1103
|
+
reportUnauthorizedReadIdentity(deps, requestedReaderName);
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
const readerName = requestedReaderName || getDefaultOrchestratorName();
|
|
1107
|
+
let relaycast;
|
|
1108
|
+
try {
|
|
1109
|
+
relaycast = await deps.createRelaycastClient({
|
|
1110
|
+
agentName: readerName,
|
|
1111
|
+
cwd: deps.getProjectRoot(),
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
catch (err) {
|
|
1115
|
+
deps.error(`Failed to initialize relaycast client: ${formatErrorDetail(err)}`);
|
|
1116
|
+
deps.exit(1);
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
try {
|
|
1120
|
+
const conversations = await relaycast.dms.conversations();
|
|
1121
|
+
const conversation = findDirectDmConversation(conversations, readerName, agent);
|
|
1122
|
+
if (!conversation) {
|
|
1123
|
+
deps.log(`No DM conversation with ${sanitizeForTerminalLine(agent)}.`);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
const rawUnreadCount = readNumber(conversation.unreadCount, conversation.unread_count);
|
|
1127
|
+
const unreadCount = normalizeUnreadCount(rawUnreadCount);
|
|
1128
|
+
const messages = sortDmMessagesChronologically((await relaycast.dms.messages(conversation.id, {
|
|
1129
|
+
limit: getFilteredDmFetchLimit(limit, unreadCount),
|
|
1130
|
+
}))
|
|
1131
|
+
.map((message) => normalizeDmMessage(message, {
|
|
1132
|
+
agentName: agent,
|
|
1133
|
+
createdAt: FALLBACK_DM_CREATED_AT,
|
|
1134
|
+
}))
|
|
1135
|
+
.filter(isPresent)
|
|
1136
|
+
.filter((message) => message.agentName === agent)
|
|
1137
|
+
.filter((message) => !sinceTs || Date.parse(message.createdAt) >= sinceTs));
|
|
1138
|
+
// `--since <id>`: only messages strictly after that id, so a
|
|
1139
|
+
// polling caller never re-receives what it already saw.
|
|
1140
|
+
const cursored = since.kind === 'cursor' ? sliceAfterCursor(messages, since.id) : messages;
|
|
1141
|
+
const hasPerMessageReadState = cursored.some((message) => message.unread !== undefined);
|
|
1142
|
+
const unreadStateUnknown = options.unread === true && rawUnreadCount === undefined && !hasPerMessageReadState;
|
|
1143
|
+
const filteredMessages = (options.unread && !unreadStateUnknown ? selectUnreadCandidates(cursored, unreadCount) : cursored).slice(-limit);
|
|
1144
|
+
if (options.json) {
|
|
1145
|
+
deps.log(JSON.stringify(filteredMessages.map((message) => ({
|
|
1146
|
+
id: message.id,
|
|
1147
|
+
from: message.agentName,
|
|
1148
|
+
to: readerName,
|
|
1149
|
+
text: message.text,
|
|
1150
|
+
createdAt: message.createdAt,
|
|
1151
|
+
direction: 'inbound',
|
|
1152
|
+
...(unreadStateUnknown ? { unread_state: 'unknown' } : {}),
|
|
1153
|
+
...(message.unread !== undefined ? { unread: message.unread } : {}),
|
|
1154
|
+
})), null, 2));
|
|
1155
|
+
}
|
|
1156
|
+
else if (!filteredMessages.length) {
|
|
1157
|
+
deps.log('No messages found.');
|
|
1158
|
+
}
|
|
1159
|
+
else {
|
|
1160
|
+
if (unreadStateUnknown) {
|
|
1161
|
+
deps.log('Unread state unavailable — showing recent inbound messages.');
|
|
1162
|
+
}
|
|
1163
|
+
for (const message of filteredMessages) {
|
|
1164
|
+
renderTranscriptMessage(deps.log, message);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
if (options.markRead && filteredMessages.length > 0) {
|
|
1168
|
+
try {
|
|
1169
|
+
await markDmMessagesRead(relaycast, conversation.id, filteredMessages);
|
|
1170
|
+
}
|
|
1171
|
+
catch (err) {
|
|
1172
|
+
deps.error(`Warning: failed to mark replies from ${sanitizeForTerminalLine(agent)} as read: ${formatErrorDetail(err)}`);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
catch (err) {
|
|
1177
|
+
deps.error(`Failed to fetch replies for ${sanitizeForTerminalLine(agent)}: ${formatErrorDetail(err)}`);
|
|
1178
|
+
deps.exit(1);
|
|
1179
|
+
}
|
|
1180
|
+
finally {
|
|
1181
|
+
await disconnectRelaycastClient(relaycast);
|
|
1182
|
+
}
|
|
619
1183
|
});
|
|
620
1184
|
}
|
|
621
1185
|
//# sourceMappingURL=messaging.js.map
|