letagents 0.12.12 → 0.12.14
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/mcp/git-remote.js +7 -7
- package/dist/mcp/local-state/agent-sessions.js +63 -5
- package/dist/mcp/local-state/local-chat.js +189 -48
- package/dist/mcp/local-state/storage.js +16 -9
- package/dist/mcp/server/daemon-tool-executor.js +92 -0
- package/dist/mcp/server/register-tools.js +4 -2
- package/dist/mcp/server/runtime/agent-sessions.js +16 -7
- package/dist/mcp/server/runtime/api.js +11 -3
- package/dist/mcp/server/runtime/daemon-tool-context.js +11 -0
- package/dist/mcp/server/runtime/execution-profile.js +1 -0
- package/dist/mcp/server/runtime/messages.js +55 -0
- package/dist/mcp/server/runtime/presence.js +3 -3
- package/dist/mcp/server/runtime/room-api.js +2 -2
- package/dist/mcp/server/runtime/room-state.js +19 -9
- package/dist/mcp/server/runtime/rooms.js +54 -33
- package/dist/mcp/server/runtime/supervisor-bridge.js +129 -11
- package/dist/mcp/server/runtime/tool-surface-policy.js +7 -0
- package/dist/mcp/server/runtime/worker-bearer.js +17 -2
- package/dist/mcp/server/runtime-contract.js +1 -0
- package/dist/mcp/server/runtime.js +6 -6
- package/dist/mcp/server/supervised-tool-facade.js +107 -6
- package/dist/mcp/server/tools/messages/read-tool.js +54 -97
- package/dist/mcp/server/tools/messages/reasoning-tool.js +2 -0
- package/dist/mcp/server/tools/messages/send-tool.js +2 -0
- package/dist/mcp/server/tools/messages/status-tool.js +2 -0
- package/dist/mcp/server/tools/messages/wait-tool.js +303 -71
- package/dist/mcp/server/tools/onboarding/status-tool.js +4 -4
- package/dist/mcp/server/tools/rooms/inspection-tools.js +15 -10
- package/dist/mcp/server/tools/rooms/repo-initialization-tool.js +2 -1
- package/dist/mcp/server/tools/tasks/board-tools.js +34 -2
- package/dist/mcp/sse-client.js +163 -20
- package/dist/shared/activation-routing.js +163 -23
- package/dist/shared/agent-presence.js +6 -0
- package/dist/shared/desktop-release-manifest.js +63 -0
- package/dist/shared/desktop-release.js +60 -0
- package/dist/shared/scoped-ids.js +6 -0
- package/package.json +7 -3
- package/shared/execution-approval-projection.d.mts +32 -0
- package/shared/execution-approval-projection.mjs +107 -0
- package/shared/execution-approval-publication-item.d.mts +20 -0
- package/shared/execution-approval-publication-item.mjs +61 -0
- package/shared/execution-approval-publication.d.mts +53 -0
- package/shared/execution-approval-publication.mjs +136 -0
- package/shared/execution-delegation-decision.d.mts +37 -0
- package/shared/execution-delegation-decision.mjs +73 -0
- package/shared/message-contracts.d.mts +32 -0
- package/shared/message-contracts.mjs +109 -0
- package/shared/room-agent-work.d.mts +31 -0
- package/shared/room-agent-work.mjs +44 -0
- package/shared/room-resource-invalidation.d.mts +38 -0
- package/shared/room-resource-invalidation.mjs +50 -0
- package/shared/routing-aliases.d.mts +18 -0
- package/shared/routing-aliases.mjs +66 -0
- package/shared/sqlite-thread-routing.d.mts +72 -0
- package/shared/sqlite-thread-routing.mjs +1038 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { getPollTimeoutCapMs } from "../../../../shared/poll-timeout-cap.js";
|
|
3
3
|
import { encodeRoomIdPath } from "../../../room-id.js";
|
|
4
|
-
import { agentSessionCredentials, appendIncludePromptOnly, buildAgentDeliveryHeaders, bindSupervisedWorkerSession, scheduleSupervisedWorkerCursorCheckpoint, currentRoom, ensureAgentIdentity, getFallbackProjectId, getLatestLocalChatMessages, getLocalChatMessages, getLastMessageId, getRememberedRoomPresence, getTargetRoomId, identityFromAgentSession, isLocalRoomStorageEnabled,
|
|
5
|
-
import { requireValidWorkerBearerRuntime, supervisedBoundedDeliveryDisabledToolResult, } from "../../runtime/worker-bearer.js";
|
|
6
|
-
import {
|
|
4
|
+
import { agentSessionCredentials, AGENT_MESSAGE_BODY_MAX_BYTES, appendIncludePromptOnly, boundAgentMessageOutput, buildAgentDeliveryHeaders, bindSupervisedWorkerSession, scheduleSupervisedWorkerCursorCheckpoint, currentRoom, ensureAgentIdentity, getFallbackProjectId, getLatestLocalChatMessages, getLocalImportedRoutingAuthority, getLocalChatMessages, getLocalChatThreadRoutingMembership, getLastMessageId, getRememberedRoomPresence, getStoredAgentRoutingStateSnapshot, getTargetRoomId, identityFromAgentSession, isLocalRoomStorageEnabled, listLocalActiveTaskOwnerLeases, resolveLocalRoomStorageIdentifiers, resolveAgentSession, roomScopedApiCall, syncRoomPresence, toAgentReadableMessages, touchRoomSession, WORKER_BEARER_AGENT_SESSION_ID, waitForLocalChatMessages, } from "../../runtime.js";
|
|
5
|
+
import { requireValidWorkerBearerRuntime, isCustodialPolling, supervisedBoundedDeliveryDisabledToolResult, } from "../../runtime/worker-bearer.js";
|
|
6
|
+
import { resolveWorkerToolIdentity } from "../../runtime/agent-sessions.js";
|
|
7
|
+
import { attachAgentMessageActivations, createGlobalAgentAddressResolver, decideAgentMessageActivation, isTaskOwnerFollowUpMessageText, } from "../../../../shared/activation-routing.js";
|
|
8
|
+
import { normalizeRoutingSender } from "../../../../../shared/routing-aliases.mjs";
|
|
7
9
|
import { findLocalMessageById, findRemoteMessageById } from "./message-lookup.js";
|
|
8
10
|
import { fetchRecentRemoteMessages } from "./read-tool.js";
|
|
9
11
|
import { jsonToolResponse } from "./response.js";
|
|
@@ -13,9 +15,9 @@ const DEFAULT_POLL_TIMEOUT_MS = 30000;
|
|
|
13
15
|
// room history (busy rooms archive millions of characters, which blows the
|
|
14
16
|
// tool's token budget). This bounds that no-cursor catch-up to a recent tail.
|
|
15
17
|
export const DEFAULT_WAIT_CATCHUP_LIMIT = 100;
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
//
|
|
18
|
+
export const MAX_WAIT_MESSAGES_PER_CALL = 100;
|
|
19
|
+
// Decide how the initial fetch should behave. Both paths return one bounded
|
|
20
|
+
// page; callers continue from last_observed_message_id when truncated.
|
|
19
21
|
export function planWaitForMessagesFetch(input) {
|
|
20
22
|
if (input.effectiveAfterMessageId) {
|
|
21
23
|
return { mode: "after_cursor", after: input.effectiveAfterMessageId };
|
|
@@ -25,23 +27,12 @@ export function planWaitForMessagesFetch(input) {
|
|
|
25
27
|
limit: input.catchupLimit ?? DEFAULT_WAIT_CATCHUP_LIMIT,
|
|
26
28
|
};
|
|
27
29
|
}
|
|
28
|
-
const LOCAL_TASK_OWNER_STATUSES = new Set(["assigned", "in_progress", "blocked", "in_review"]);
|
|
29
30
|
export function buildWaitForMessagesRequestOptions(input) {
|
|
30
31
|
return {
|
|
31
32
|
...(input.signal ? { signal: input.signal } : {}),
|
|
32
33
|
headers: input.deliveryHeaders,
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
|
-
export function buildWaitForMessagesHistoryPageRequest(input) {
|
|
36
|
-
const querySuffix = input.queryString ? `?${input.queryString}` : "";
|
|
37
|
-
return {
|
|
38
|
-
room_id: input.targetRoomId,
|
|
39
|
-
project_id: input.targetProjectId,
|
|
40
|
-
room_path: (targetRoomId) => appendIncludePromptOnly(`/rooms/${encodeRoomIdPath(targetRoomId)}/messages${querySuffix}`),
|
|
41
|
-
project_path: (targetProjectId) => appendIncludePromptOnly(`/projects/${encodeURIComponent(targetProjectId)}/messages${querySuffix}`),
|
|
42
|
-
options: buildWaitForMessagesRequestOptions({ deliveryHeaders: input.deliveryHeaders }),
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
36
|
function isRecord(value) {
|
|
46
37
|
return Boolean(value && typeof value === "object");
|
|
47
38
|
}
|
|
@@ -118,41 +109,233 @@ export function resolveWaitAgentSession(roomId, agentSessionId) {
|
|
|
118
109
|
return resolveAgentSession(roomId, agentSessionId);
|
|
119
110
|
}
|
|
120
111
|
export async function localActivationContext(roomId) {
|
|
121
|
-
const result = await listLocalTasks(roomId, { openOnly: true });
|
|
122
112
|
return {
|
|
123
|
-
activeTaskLeases:
|
|
124
|
-
.flatMap((task) => {
|
|
125
|
-
const agentKey = task.assignee_agent_key?.trim();
|
|
126
|
-
if (!LOCAL_TASK_OWNER_STATUSES.has(task.status) || !agentKey) {
|
|
127
|
-
return [];
|
|
128
|
-
}
|
|
129
|
-
return [{
|
|
130
|
-
kind: "work",
|
|
131
|
-
status: "active",
|
|
132
|
-
actor_label: task.assignee || agentKey,
|
|
133
|
-
agent_key: agentKey,
|
|
134
|
-
agent_instance_id: task.assignee_agent_instance_id,
|
|
135
|
-
agent_session_id: task.assignee_agent_session_id,
|
|
136
|
-
}];
|
|
137
|
-
}),
|
|
113
|
+
activeTaskLeases: await listLocalActiveTaskOwnerLeases(roomId),
|
|
138
114
|
};
|
|
139
115
|
}
|
|
140
|
-
async function attachLocalActivationMetadata(roomId, messages, agentSession, options = {}) {
|
|
116
|
+
export async function attachLocalActivationMetadata(roomId, messages, agentSession, options = {}) {
|
|
141
117
|
if (!agentSession || agentSession.session_kind !== "worker") {
|
|
142
118
|
return messages;
|
|
143
119
|
}
|
|
144
120
|
const records = messages.filter(isRecord);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return attachAgentMessageActivations(records, {
|
|
121
|
+
if (records.length === 0)
|
|
122
|
+
return messages;
|
|
123
|
+
const identity = {
|
|
149
124
|
actor_label: agentSession.actor_label,
|
|
150
125
|
agent_key: agentSession.agent_key,
|
|
151
126
|
agent_instance_id: agentSession.agent_instance_id ?? null,
|
|
152
127
|
agent_session_id: agentSession.session_id,
|
|
153
128
|
display_name: agentSession.display_name,
|
|
154
129
|
session_kind: agentSession.session_kind,
|
|
155
|
-
}
|
|
130
|
+
};
|
|
131
|
+
// Imported cloud rows carry immutable, account-scoped send-time authority.
|
|
132
|
+
// A present wrapper always wins over mutable local aliases, including when
|
|
133
|
+
// the current state population cannot be read completely.
|
|
134
|
+
const activeSessionRoomId = options.activeSessionRoomId?.trim() || roomId;
|
|
135
|
+
const routingState = getStoredAgentRoutingStateSnapshot(activeSessionRoomId);
|
|
136
|
+
const authoritativeLegacyDecisions = new Map();
|
|
137
|
+
const legacyRecords = [];
|
|
138
|
+
const normalizedIdentityKey = normalizeRoutingSender(identity.agent_key);
|
|
139
|
+
const validActivationReasons = new Set([
|
|
140
|
+
"self_message",
|
|
141
|
+
"explicit_mention",
|
|
142
|
+
"explicit_other_mention",
|
|
143
|
+
"broadcast",
|
|
144
|
+
"reply_target",
|
|
145
|
+
"other_reply_target",
|
|
146
|
+
"thread_participant",
|
|
147
|
+
"task_owner",
|
|
148
|
+
"system_event",
|
|
149
|
+
"unaddressed",
|
|
150
|
+
]);
|
|
151
|
+
for (const message of records) {
|
|
152
|
+
const id = messageId(message);
|
|
153
|
+
if (!id)
|
|
154
|
+
continue;
|
|
155
|
+
const imported = getLocalImportedRoutingAuthority(message);
|
|
156
|
+
if (!imported) {
|
|
157
|
+
if (routingState.complete) {
|
|
158
|
+
legacyRecords.push(message);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
authoritativeLegacyDecisions.set(id, {
|
|
162
|
+
decision: "silent",
|
|
163
|
+
reason: "unaddressed",
|
|
164
|
+
addressed: false,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (!routingState.accountReaderKey
|
|
170
|
+
|| imported.readerKey !== routingState.accountReaderKey
|
|
171
|
+
|| imported.routing.authority === "invalid") {
|
|
172
|
+
authoritativeLegacyDecisions.set(id, {
|
|
173
|
+
decision: "silent",
|
|
174
|
+
reason: "unaddressed",
|
|
175
|
+
addressed: false,
|
|
176
|
+
});
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const target = imported.routing.recipientSessions.find((candidate) => candidate.agentKey === normalizedIdentityKey);
|
|
180
|
+
const targetSessionId = imported.routing.authority === "receipts"
|
|
181
|
+
? target && "successorAgentSessionId" in target
|
|
182
|
+
? target.successorAgentSessionId ?? target.agentSessionId
|
|
183
|
+
: target?.agentSessionId
|
|
184
|
+
: target?.agentSessionId;
|
|
185
|
+
if (!target || targetSessionId !== identity.agent_session_id) {
|
|
186
|
+
authoritativeLegacyDecisions.set(id, {
|
|
187
|
+
decision: "silent",
|
|
188
|
+
reason: "unaddressed",
|
|
189
|
+
addressed: false,
|
|
190
|
+
});
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const importedReason = imported.routing.authority === "legacy"
|
|
194
|
+
&& "activationReason" in target
|
|
195
|
+
&& validActivationReasons.has(target.activationReason)
|
|
196
|
+
? target.activationReason
|
|
197
|
+
: "explicit_mention";
|
|
198
|
+
authoritativeLegacyDecisions.set(id, {
|
|
199
|
+
decision: "activate",
|
|
200
|
+
reason: importedReason,
|
|
201
|
+
addressed: true,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
if (legacyRecords.length === 0) {
|
|
205
|
+
return attachAgentMessageActivations(records, identity, { authoritativeLegacyDecisions });
|
|
206
|
+
}
|
|
207
|
+
const threadRootIds = legacyRecords.flatMap((message) => {
|
|
208
|
+
const messageId = typeof message.id === "string" ? message.id : "";
|
|
209
|
+
const thread = isRecord(message.thread) ? message.thread : null;
|
|
210
|
+
const rootId = typeof message.thread_root_id === "string"
|
|
211
|
+
? message.thread_root_id
|
|
212
|
+
: typeof thread?.root_message_id === "string"
|
|
213
|
+
? thread.root_message_id
|
|
214
|
+
: "";
|
|
215
|
+
return rootId && rootId !== messageId ? [rootId] : [];
|
|
216
|
+
});
|
|
217
|
+
// Linked local/cloud rooms store messages under the SQLite room id while
|
|
218
|
+
// registered workers remain keyed by the canonical cloud room id. Routing
|
|
219
|
+
// ambiguity must always use that complete canonical population.
|
|
220
|
+
const storedActiveSessions = routingState.sessions;
|
|
221
|
+
const activeIdentities = storedActiveSessions.map((session) => ({
|
|
222
|
+
actor_label: session.actor_label,
|
|
223
|
+
agent_key: session.agent_key,
|
|
224
|
+
agent_instance_id: session.agent_instance_id ?? null,
|
|
225
|
+
agent_session_id: session.session_id,
|
|
226
|
+
display_name: session.display_name,
|
|
227
|
+
session_kind: session.session_kind,
|
|
228
|
+
}));
|
|
229
|
+
if (!activeIdentities.some((candidate) => candidate.agent_session_id === identity.agent_session_id
|
|
230
|
+
&& candidate.agent_key === identity.agent_key)) {
|
|
231
|
+
// The request's authenticated current session is authoritative even when
|
|
232
|
+
// an older local-state file has not persisted it yet.
|
|
233
|
+
activeIdentities.push(identity);
|
|
234
|
+
}
|
|
235
|
+
const sameKeySessions = storedActiveSessions.filter((session) => session.agent_key === identity.agent_key);
|
|
236
|
+
const currentRepresentativeSessionId = sameKeySessions[0]?.session_id
|
|
237
|
+
?? identity.agent_session_id;
|
|
238
|
+
const currentIsActive = currentRepresentativeSessionId === identity.agent_session_id;
|
|
239
|
+
const resolveGlobalAddress = createGlobalAgentAddressResolver(activeIdentities);
|
|
240
|
+
const explicitMentionMessageIds = new Set();
|
|
241
|
+
const replyTargetMessageIds = new Set();
|
|
242
|
+
const exactReplyTargetMessageIds = new Set();
|
|
243
|
+
const selfMessageIds = new Set();
|
|
244
|
+
for (const message of legacyRecords) {
|
|
245
|
+
const messageId = typeof message.id === "string" ? message.id : "";
|
|
246
|
+
if (!messageId)
|
|
247
|
+
continue;
|
|
248
|
+
const addressed = resolveGlobalAddress(message);
|
|
249
|
+
const reply = isRecord(message.reply_to) ? message.reply_to : null;
|
|
250
|
+
const replyPublisherIdentity = isRecord(reply?.agent_identity)
|
|
251
|
+
? reply.agent_identity
|
|
252
|
+
: null;
|
|
253
|
+
const replyPublisherKey = typeof replyPublisherIdentity?.agent_key === "string"
|
|
254
|
+
? replyPublisherIdentity.agent_key.trim()
|
|
255
|
+
: "";
|
|
256
|
+
const replyPublisherSessionId = typeof replyPublisherIdentity?.agent_session_id === "string"
|
|
257
|
+
? replyPublisherIdentity.agent_session_id.trim()
|
|
258
|
+
: "";
|
|
259
|
+
if (replyPublisherKey) {
|
|
260
|
+
addressed.replyTargetKeys.clear();
|
|
261
|
+
const exactReplyIdentity = replyPublisherSessionId
|
|
262
|
+
? activeIdentities.find((candidate) => candidate.agent_key === replyPublisherKey
|
|
263
|
+
&& candidate.agent_session_id === replyPublisherSessionId)
|
|
264
|
+
: undefined;
|
|
265
|
+
if (exactReplyIdentity) {
|
|
266
|
+
if (exactReplyIdentity.agent_session_id === identity.agent_session_id) {
|
|
267
|
+
exactReplyTargetMessageIds.add(messageId);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else if (activeIdentities.some((candidate) => candidate.agent_key === replyPublisherKey)) {
|
|
271
|
+
addressed.replyTargetKeys.add(replyPublisherKey);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const publisherIdentity = isRecord(message.agent_identity)
|
|
275
|
+
? message.agent_identity
|
|
276
|
+
: null;
|
|
277
|
+
const publisherAgentKey = typeof publisherIdentity?.agent_key === "string"
|
|
278
|
+
? publisherIdentity.agent_key.trim()
|
|
279
|
+
: "";
|
|
280
|
+
if (String(message.source ?? "").trim() === "agent"
|
|
281
|
+
&& (publisherAgentKey
|
|
282
|
+
? publisherAgentKey === identity.agent_key
|
|
283
|
+
: addressed.senderKeys.has(identity.agent_key))) {
|
|
284
|
+
selfMessageIds.add(messageId);
|
|
285
|
+
}
|
|
286
|
+
if (addressed.explicitMentionKeys.has(identity.agent_key)) {
|
|
287
|
+
explicitMentionMessageIds.add(messageId);
|
|
288
|
+
}
|
|
289
|
+
const thread = isRecord(message.thread) ? message.thread : null;
|
|
290
|
+
const rootId = typeof message.thread_root_id === "string"
|
|
291
|
+
? message.thread_root_id
|
|
292
|
+
: typeof thread?.root_message_id === "string"
|
|
293
|
+
? thread.root_message_id
|
|
294
|
+
: "";
|
|
295
|
+
if ((!rootId || rootId === messageId)
|
|
296
|
+
&& (exactReplyTargetMessageIds.has(messageId)
|
|
297
|
+
|| addressed.replyTargetKeys.has(identity.agent_key))) {
|
|
298
|
+
replyTargetMessageIds.add(messageId);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const [activationContext, projectedThreadParticipantRootIds] = await Promise.all([
|
|
302
|
+
options.includeTaskOwnerLeases === false
|
|
303
|
+
|| !records.some((message) => isTaskOwnerFollowUpMessageText(message.text))
|
|
304
|
+
? undefined
|
|
305
|
+
: localActivationContext(roomId),
|
|
306
|
+
getLocalChatThreadRoutingMembership(roomId, threadRootIds, identity, activeIdentities),
|
|
307
|
+
]);
|
|
308
|
+
const exactContext = {
|
|
309
|
+
...activationContext,
|
|
310
|
+
selfMessageIds,
|
|
311
|
+
threadParticipantRootIds: projectedThreadParticipantRootIds,
|
|
312
|
+
explicitMentionMessageIds,
|
|
313
|
+
replyTargetMessageIds,
|
|
314
|
+
};
|
|
315
|
+
const exactTaskSessionIdsForKey = new Set((activationContext?.activeTaskLeases ?? [])
|
|
316
|
+
.filter((lease) => lease.status === "active"
|
|
317
|
+
&& lease.agent_key === identity.agent_key
|
|
318
|
+
&& Boolean(lease.agent_session_id))
|
|
319
|
+
.map((lease) => lease.agent_session_id));
|
|
320
|
+
for (const message of legacyRecords) {
|
|
321
|
+
const messageId = typeof message.id === "string" ? message.id : "";
|
|
322
|
+
if (!messageId)
|
|
323
|
+
continue;
|
|
324
|
+
const resolved = decideAgentMessageActivation(message, identity, exactContext);
|
|
325
|
+
const exactTaskOwner = resolved.reason === "task_owner"
|
|
326
|
+
&& activationContext?.activeTaskLeases?.some((lease) => lease.status === "active"
|
|
327
|
+
&& lease.agent_session_id === identity.agent_session_id
|
|
328
|
+
&& (!lease.agent_key || lease.agent_key === identity.agent_key));
|
|
329
|
+
const eligibleRepresentative = exactReplyTargetMessageIds.has(messageId)
|
|
330
|
+
? true
|
|
331
|
+
: resolved.reason === "task_owner" && exactTaskSessionIdsForKey.size > 0
|
|
332
|
+
? exactTaskSessionIdsForKey.size === 1 && exactTaskOwner
|
|
333
|
+
: currentIsActive;
|
|
334
|
+
authoritativeLegacyDecisions.set(messageId, eligibleRepresentative && resolved.decision === "activate"
|
|
335
|
+
? resolved
|
|
336
|
+
: { decision: "silent", reason: "unaddressed", addressed: false });
|
|
337
|
+
}
|
|
338
|
+
return attachAgentMessageActivations(records, identity, { authoritativeLegacyDecisions });
|
|
156
339
|
}
|
|
157
340
|
// Resolving an out-of-window parent costs a lookup (a by-id fetch, or a
|
|
158
341
|
// page-by-page history scan against older APIs), so resolved (and observed)
|
|
@@ -162,6 +345,9 @@ async function attachLocalActivationMetadata(roomId, messages, agentSession, opt
|
|
|
162
345
|
// as immutable once posted; a bounded insertion-ordered map keeps memory flat
|
|
163
346
|
// for long-running workers.
|
|
164
347
|
const THREAD_CONTEXT_CACHE_MAX = 500;
|
|
348
|
+
export const THREAD_CONTEXT_LOOKUP_MAX = 16;
|
|
349
|
+
export const THREAD_CONTEXT_BYTES_MAX = 256 * 1024;
|
|
350
|
+
const THREAD_CONTEXT_DEADLINE_MS = 1_000;
|
|
165
351
|
const threadContextCache = new Map();
|
|
166
352
|
function threadContextCacheKey(scopeId, targetMessageId) {
|
|
167
353
|
return `${scopeId}:${targetMessageId}`;
|
|
@@ -199,20 +385,30 @@ export async function collectThreadContextMessages(input) {
|
|
|
199
385
|
if (pendingIds.length === 0) {
|
|
200
386
|
// Nothing quotes an out-of-window message (idle polls land here), so skip
|
|
201
387
|
// storage-mode resolution entirely.
|
|
202
|
-
return [];
|
|
388
|
+
return { messages: [], truncated: false };
|
|
203
389
|
}
|
|
204
390
|
const contextMessages = [];
|
|
391
|
+
let contextBytes = 0;
|
|
392
|
+
let lookups = 0;
|
|
393
|
+
let truncated = false;
|
|
394
|
+
const deadline = Date.now() + THREAD_CONTEXT_DEADLINE_MS;
|
|
205
395
|
const useLocalStorage = Boolean(input.localRoomId && await isLocalRoomStorageEnabled(input.localRoomId));
|
|
206
396
|
const localIdentifiers = useLocalStorage
|
|
207
397
|
? await resolveLocalRoomStorageIdentifiers(input.localRoomId)
|
|
208
398
|
: { localRoomId: input.localRoomId };
|
|
209
399
|
const sqliteRoomId = localIdentifiers.localRoomId || input.localRoomId;
|
|
210
400
|
while (pendingIds.length > 0) {
|
|
401
|
+
if (lookups >= THREAD_CONTEXT_LOOKUP_MAX || Date.now() >= deadline) {
|
|
402
|
+
truncated = true;
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
211
405
|
const nextId = pendingIds.shift();
|
|
212
406
|
if (!nextId || seenIds.has(nextId))
|
|
213
407
|
continue;
|
|
214
408
|
seenIds.add(nextId);
|
|
215
409
|
const cached = threadContextCache.get(threadContextCacheKey(scopeId, nextId));
|
|
410
|
+
if (!cached)
|
|
411
|
+
lookups += 1;
|
|
216
412
|
const message = cached
|
|
217
413
|
?? (useLocalStorage && input.localRoomId
|
|
218
414
|
? await findLocalMessageById(sqliteRoomId || input.localRoomId, nextId)
|
|
@@ -226,13 +422,19 @@ export async function collectThreadContextMessages(input) {
|
|
|
226
422
|
if (!cached) {
|
|
227
423
|
rememberThreadContextMessages(scopeId, [message]);
|
|
228
424
|
}
|
|
425
|
+
const messageBytes = Buffer.byteLength(JSON.stringify(message), "utf8");
|
|
426
|
+
if (contextBytes + messageBytes > THREAD_CONTEXT_BYTES_MAX) {
|
|
427
|
+
truncated = true;
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
contextBytes += messageBytes;
|
|
229
431
|
contextMessages.push(message);
|
|
230
432
|
const parentId = replyReferenceId(message);
|
|
231
433
|
if (parentId && !seenIds.has(parentId)) {
|
|
232
434
|
pendingIds.push(parentId);
|
|
233
435
|
}
|
|
234
436
|
}
|
|
235
|
-
return contextMessages;
|
|
437
|
+
return { messages: contextMessages, truncated };
|
|
236
438
|
}
|
|
237
439
|
export function registerWaitForMessagesTool(server) {
|
|
238
440
|
server.tool("wait_for_messages", "Wait for new messages in a Let Agents Chat room (HTTP long-poll). Messages labeled activation.for_current_agent.decision=\"silent\" are skipped for the current worker; when last_observed_message_id is present, use it as cursor progress even if messages is empty. Threaded replies include thread_parent_id/thread.root_message_id; use send_thread_message with that id to keep focused side discussion out of the main room. For multi-hour runs, call in a loop: always pass after_message_id from the last message you processed or last_observed_message_id so an empty result means 'nothing new yet', not 'stop working'. Legacy/manual room workers may send a brief continue instruction when another legacy/manual participant posts a premature 'I will wait' closing line. Never send that nudge to or about a daemon-supervised participant, and daemon-supervised workers must not emit it; their supervisor owns wake and retry. Per-call wait is capped (default max 180s unless LETAGENTS_POLL_MAX_MS is set on API and MCP).", {
|
|
@@ -260,10 +462,21 @@ export function registerWaitForMessagesTool(server) {
|
|
|
260
462
|
const targetRoomId = getTargetRoomId(room_id);
|
|
261
463
|
const targetProjectId = getFallbackProjectId();
|
|
262
464
|
const localRoomId = targetRoomId ?? currentRoom?.room_id ?? targetProjectId;
|
|
263
|
-
const identity = await ensureAgentIdentity();
|
|
264
465
|
const sessionRoomId = targetRoomId ?? currentRoom?.room_id ?? localRoomId ?? null;
|
|
265
|
-
const
|
|
266
|
-
|
|
466
|
+
const custodial = isCustodialPolling();
|
|
467
|
+
const routingStateSnapshot = custodial ? { complete: true } : getStoredAgentRoutingStateSnapshot(sessionRoomId ?? "");
|
|
468
|
+
const localStorageEnabled = Boolean(localRoomId && await isLocalRoomStorageEnabled(localRoomId));
|
|
469
|
+
if (localStorageEnabled && !routingStateSnapshot.complete) {
|
|
470
|
+
throw new Error("Local agent routing state is unavailable; retry after restoring the state file.");
|
|
471
|
+
}
|
|
472
|
+
const exactIdentity = custodial ? await resolveWorkerToolIdentity({ roomId: sessionRoomId, agentSessionId: agent_session_id }) : null;
|
|
473
|
+
const identity = exactIdentity?.identity ?? await ensureAgentIdentity();
|
|
474
|
+
const agentSession = exactIdentity?.agentSession ?? resolveWaitAgentSession(sessionRoomId, agent_session_id);
|
|
475
|
+
if (custodial) {
|
|
476
|
+
if (!agentSession || !after_message_id)
|
|
477
|
+
throw new Error("Custodial polling requires exact worker identity and durable cursor.");
|
|
478
|
+
}
|
|
479
|
+
else if (agentSession) {
|
|
267
480
|
// Registration (or a successor generation) must bind strictly once.
|
|
268
481
|
// Later waits use a read-only exact verification capped at 250ms, so a
|
|
269
482
|
// wedged daemon cannot consume the room-poll budget and an old worker
|
|
@@ -278,8 +491,8 @@ export function registerWaitForMessagesTool(server) {
|
|
|
278
491
|
}
|
|
279
492
|
const maxPollMs = getPollTimeoutCapMs();
|
|
280
493
|
const serverTimeout = Math.min(Math.max(timeout || DEFAULT_POLL_TIMEOUT_MS, 1000), maxPollMs);
|
|
281
|
-
if (localRoomId &&
|
|
282
|
-
const { localRoomId: sqliteRoomId } = await resolveLocalRoomStorageIdentifiers(localRoomId);
|
|
494
|
+
if (localRoomId && localStorageEnabled) {
|
|
495
|
+
const { localRoomId: sqliteRoomId, cloudRoomId, } = await resolveLocalRoomStorageIdentifiers(localRoomId);
|
|
283
496
|
const effectiveLocalRoomId = sqliteRoomId || localRoomId;
|
|
284
497
|
const effectiveAfterMessageId = resolveEffectiveAfterMessageId({
|
|
285
498
|
requestedAfterMessageId: after_message_id,
|
|
@@ -290,11 +503,12 @@ export function registerWaitForMessagesTool(server) {
|
|
|
290
503
|
// keep the unchanged forward "everything after the cursor" behavior.
|
|
291
504
|
const existing = fetchPlan.mode === "catch_up_tail"
|
|
292
505
|
? await getLatestLocalChatMessages(effectiveLocalRoomId, {
|
|
293
|
-
limit: fetchPlan.limit,
|
|
506
|
+
limit: Math.min(fetchPlan.limit, MAX_WAIT_MESSAGES_PER_CALL),
|
|
294
507
|
include_prompt_only: true,
|
|
295
508
|
})
|
|
296
509
|
: await getLocalChatMessages(effectiveLocalRoomId, {
|
|
297
510
|
after: effectiveAfterMessageId,
|
|
511
|
+
limit: MAX_WAIT_MESSAGES_PER_CALL,
|
|
298
512
|
include_prompt_only: true,
|
|
299
513
|
});
|
|
300
514
|
const replayingExistingMessages = existing.messages.length > 0;
|
|
@@ -303,12 +517,18 @@ export function registerWaitForMessagesTool(server) {
|
|
|
303
517
|
: await waitForLocalChatMessages(effectiveLocalRoomId, {
|
|
304
518
|
after: effectiveAfterMessageId,
|
|
305
519
|
timeoutMs: serverTimeout,
|
|
520
|
+
limit: MAX_WAIT_MESSAGES_PER_CALL,
|
|
306
521
|
include_prompt_only: true,
|
|
307
522
|
});
|
|
308
523
|
const messages = await attachLocalActivationMetadata(effectiveLocalRoomId, result.messages, agentSession, {
|
|
309
524
|
includeTaskOwnerLeases: !replayingExistingMessages,
|
|
525
|
+
activeSessionRoomId: cloudRoomId || sessionRoomId,
|
|
526
|
+
});
|
|
527
|
+
const bounded = boundAgentMessageOutput(messages, {
|
|
528
|
+
direction: fetchPlan.mode === "catch_up_tail" ? "suffix" : "prefix",
|
|
529
|
+
maxBytes: AGENT_MESSAGE_BODY_MAX_BYTES,
|
|
310
530
|
});
|
|
311
|
-
const routing = filterSilentActivationMessages(messages);
|
|
531
|
+
const routing = filterSilentActivationMessages(bounded.messages);
|
|
312
532
|
const observedCursor = routing.last_observed_message_id ?? getLastMessageId(result);
|
|
313
533
|
touchRoomSession(effectiveLocalRoomId, observedCursor);
|
|
314
534
|
const threadContext = await collectThreadContextMessages({
|
|
@@ -320,8 +540,13 @@ export function registerWaitForMessagesTool(server) {
|
|
|
320
540
|
return jsonToolResponse({
|
|
321
541
|
room_id: effectiveLocalRoomId,
|
|
322
542
|
...addActivationRoutingTelemetry({
|
|
323
|
-
messages: toAgentReadableMessages(routing.messages, threadContext),
|
|
543
|
+
messages: toAgentReadableMessages(routing.messages, threadContext.messages),
|
|
324
544
|
}, routing),
|
|
545
|
+
...(result.has_more || bounded.truncated ? { truncated: true } : {}),
|
|
546
|
+
...(bounded.omittedMessageCount > 0
|
|
547
|
+
? { omitted_message_count: bounded.omittedMessageCount }
|
|
548
|
+
: {}),
|
|
549
|
+
...(threadContext.truncated ? { thread_context_truncated: true } : {}),
|
|
325
550
|
});
|
|
326
551
|
}
|
|
327
552
|
await syncRoomPresence(targetRoomId ?? currentRoom?.room_id ?? null, identity, getRememberedRoomPresence(targetRoomId ?? currentRoom?.room_id ?? null, agentSession ? identityFromAgentSession(agentSession) : identity), agentSession);
|
|
@@ -333,6 +558,8 @@ export function registerWaitForMessagesTool(server) {
|
|
|
333
558
|
const deliveryHeaders = buildAgentDeliveryHeaders(agentSession);
|
|
334
559
|
const allMessages = [];
|
|
335
560
|
let roomIdFromResponse;
|
|
561
|
+
let catchUpTruncated = false;
|
|
562
|
+
let apiObservedCursor = null;
|
|
336
563
|
// Long-poll the server (blocks up to serverTimeout for new messages),
|
|
337
564
|
// optionally seeded with a cursor. Shared by the cursor path and by the
|
|
338
565
|
// no-cursor empty-tail fallback so a quiet room still blocks instead of
|
|
@@ -342,6 +569,7 @@ export function registerWaitForMessagesTool(server) {
|
|
|
342
569
|
const params = new URLSearchParams();
|
|
343
570
|
if (after)
|
|
344
571
|
params.set("after", after);
|
|
572
|
+
params.set("limit", String(MAX_WAIT_MESSAGES_PER_CALL));
|
|
345
573
|
params.set("timeout", String(serverTimeout));
|
|
346
574
|
const queryString = params.toString();
|
|
347
575
|
const firstResult = await roomScopedApiCall({
|
|
@@ -349,6 +577,7 @@ export function registerWaitForMessagesTool(server) {
|
|
|
349
577
|
project_id: targetProjectId,
|
|
350
578
|
room_path: (targetRoomId) => appendIncludePromptOnly(`/rooms/${encodeRoomIdPath(targetRoomId)}/messages/poll?${queryString}`),
|
|
351
579
|
project_path: (targetProjectId) => appendIncludePromptOnly(`/projects/${encodeURIComponent(targetProjectId)}/messages/poll?${queryString}`),
|
|
580
|
+
preserve_session_cursor: true,
|
|
352
581
|
options: buildWaitForMessagesRequestOptions({
|
|
353
582
|
deliveryHeaders,
|
|
354
583
|
signal: AbortSignal.timeout(clientTimeout),
|
|
@@ -356,25 +585,10 @@ export function registerWaitForMessagesTool(server) {
|
|
|
356
585
|
});
|
|
357
586
|
allMessages.push(...(firstResult.messages ?? []));
|
|
358
587
|
roomIdFromResponse = roomIdFromResponse || firstResult.room_id || firstResult.project_id;
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
pageParams.set("after", afterCursor);
|
|
364
|
-
const qs = pageParams.toString();
|
|
365
|
-
const page = await roomScopedApiCall(buildWaitForMessagesHistoryPageRequest({
|
|
366
|
-
targetRoomId,
|
|
367
|
-
targetProjectId,
|
|
368
|
-
queryString: qs,
|
|
369
|
-
deliveryHeaders,
|
|
370
|
-
}));
|
|
371
|
-
const msgs = page.messages ?? [];
|
|
372
|
-
allMessages.push(...msgs);
|
|
373
|
-
if (!page.has_more || msgs.length === 0)
|
|
374
|
-
break;
|
|
375
|
-
afterCursor = msgs[msgs.length - 1]?.id;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
588
|
+
catchUpTruncated = Boolean(firstResult.has_more);
|
|
589
|
+
apiObservedCursor = typeof firstResult.last_observed_message_id === "string"
|
|
590
|
+
? firstResult.last_observed_message_id
|
|
591
|
+
: null;
|
|
378
592
|
};
|
|
379
593
|
if (fetchPlan.mode === "catch_up_tail") {
|
|
380
594
|
// No cursor: catch up on only the bounded recent tail. Mirror
|
|
@@ -385,6 +599,7 @@ export function registerWaitForMessagesTool(server) {
|
|
|
385
599
|
targetRoomId,
|
|
386
600
|
targetProjectId,
|
|
387
601
|
limit: fetchPlan.limit,
|
|
602
|
+
deliveryHeaders,
|
|
388
603
|
});
|
|
389
604
|
if (recent.messages.length > 0) {
|
|
390
605
|
allMessages.push(...recent.messages);
|
|
@@ -402,7 +617,11 @@ export function registerWaitForMessagesTool(server) {
|
|
|
402
617
|
else {
|
|
403
618
|
await longPollFromCursor(fetchPlan.after);
|
|
404
619
|
}
|
|
405
|
-
const
|
|
620
|
+
const bounded = boundAgentMessageOutput(allMessages, {
|
|
621
|
+
direction: fetchPlan.mode === "catch_up_tail" ? "suffix" : "prefix",
|
|
622
|
+
maxBytes: AGENT_MESSAGE_BODY_MAX_BYTES,
|
|
623
|
+
});
|
|
624
|
+
const routing = filterSilentActivationMessages(bounded.messages);
|
|
406
625
|
const threadContext = await collectThreadContextMessages({
|
|
407
626
|
messages: routing.messages,
|
|
408
627
|
localRoomId,
|
|
@@ -410,13 +629,26 @@ export function registerWaitForMessagesTool(server) {
|
|
|
410
629
|
projectId: targetProjectId,
|
|
411
630
|
});
|
|
412
631
|
const output = addActivationRoutingTelemetry({
|
|
413
|
-
messages: toAgentReadableMessages(routing.messages, threadContext),
|
|
632
|
+
messages: toAgentReadableMessages(routing.messages, threadContext.messages),
|
|
414
633
|
}, routing);
|
|
634
|
+
if (threadContext.truncated)
|
|
635
|
+
output.thread_context_truncated = true;
|
|
415
636
|
if (roomIdFromResponse) {
|
|
416
637
|
output[targetRoomId ? "room_id" : "project_id"] = roomIdFromResponse;
|
|
417
638
|
}
|
|
639
|
+
if (catchUpTruncated || bounded.truncated)
|
|
640
|
+
output.truncated = true;
|
|
641
|
+
if (bounded.omittedMessageCount > 0) {
|
|
642
|
+
output.omitted_message_count = bounded.omittedMessageCount;
|
|
643
|
+
}
|
|
644
|
+
// The API cursor can cover concealed messages, but not visible messages
|
|
645
|
+
// omitted by our own byte bound. Resume after the retained page instead.
|
|
646
|
+
const observedCursor = !bounded.truncated && apiObservedCursor
|
|
647
|
+
? apiObservedCursor
|
|
648
|
+
: routing.last_observed_message_id ?? undefined;
|
|
649
|
+
if (observedCursor)
|
|
650
|
+
output.last_observed_message_id = observedCursor;
|
|
418
651
|
if (targetRoomId) {
|
|
419
|
-
const observedCursor = routing.last_observed_message_id ?? getLastMessageId(output);
|
|
420
652
|
touchRoomSession(targetRoomId, observedCursor);
|
|
421
653
|
if (allMessages.length > 0 && agentSession) {
|
|
422
654
|
const firstMsg = allMessages[0];
|
|
@@ -83,7 +83,7 @@ export function registerGetOnboardingStatusTool(server) {
|
|
|
83
83
|
const storedAuth = getStoredAuth();
|
|
84
84
|
const pendingAuth = getPendingDeviceAuth();
|
|
85
85
|
const savedCurrentRoom = getStoredCurrentRoom();
|
|
86
|
-
const detectedRoom = configGitContext?.
|
|
86
|
+
const detectedRoom = configGitContext?.activeRoomLocator ?? gitContext?.activeRoomLocator ?? null;
|
|
87
87
|
const api_health = await checkOnboardingApiHealth();
|
|
88
88
|
const authenticated = Boolean(process.env.LETAGENTS_TOKEN || storedAuth);
|
|
89
89
|
let nextStep = "join_room";
|
|
@@ -113,10 +113,10 @@ export function registerGetOnboardingStatusTool(server) {
|
|
|
113
113
|
saved_current_room: toPublicStoredRoomSession(savedCurrentRoom),
|
|
114
114
|
detected_room_from_context: detectedRoom,
|
|
115
115
|
configured_room_from_file: configRoom,
|
|
116
|
-
configured_active_room_from_context: configGitContext?.
|
|
116
|
+
configured_active_room_from_context: configGitContext?.activeRoomLocator ?? null,
|
|
117
117
|
derived_repo_room_from_git: gitContext?.repoRoom ?? null,
|
|
118
|
-
derived_active_git_room: gitContext?.
|
|
119
|
-
derived_branch_room_from_git: gitContext?.
|
|
118
|
+
derived_active_git_room: gitContext?.activeRoomLocator ?? null,
|
|
119
|
+
derived_branch_room_from_git: gitContext?.activeRefRoomLocator ?? null,
|
|
120
120
|
git_current_branch: gitContext?.currentBranch ?? configGitContext?.currentBranch ?? null,
|
|
121
121
|
git_default_branch: gitContext?.defaultBranch ?? configGitContext?.defaultBranch ?? null,
|
|
122
122
|
repo_root: repoRoot,
|
|
@@ -3,8 +3,10 @@ import { join } from "path";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { getRoomFromConfig } from "../../../config-reader.js";
|
|
5
5
|
import { buildActiveGitRoomContext, getGitCurrentBranch, getGitDefaultBranch, getGitRoomContext, } from "../../../git-remote.js";
|
|
6
|
-
import { currentAgentIdentity, currentAgentIdentityKey, currentRoom, getConversationIdentity, getCurrentLiveSessionPayload, getStoredAgentIdentity, listStoredCodexLiveSessions, toPublicAgentIdentity, toPublicCurrentRoomState, withJoinRoomAgentPrompt, } from "../../runtime.js";
|
|
6
|
+
import { currentAgentIdentity, currentAgentIdentityKey, currentRoomMatchesLocator, currentRoom, getConversationIdentity, getCurrentLiveSessionPayload, getStoredAgentIdentity, listStoredCodexLiveSessions, toPublicAgentIdentity, toPublicCurrentRoomState, withJoinRoomAgentPrompt, } from "../../runtime.js";
|
|
7
7
|
import { requireValidWorkerBearerRuntime } from "../../runtime/worker-bearer.js";
|
|
8
|
+
import { getDaemonToolExecutionContext, getRuntimeWorkingDirectory } from "../../runtime/daemon-tool-context.js";
|
|
9
|
+
import { identityFromAgentSession } from "../../runtime/agent-sessions.js";
|
|
8
10
|
import { findExistingConfig, resolveGitRoot, } from "../../repo-context.js";
|
|
9
11
|
import { jsonToolResponse } from "./response.js";
|
|
10
12
|
let ownerAuthStoreLoader = () => import("../../../local-state.js");
|
|
@@ -29,6 +31,7 @@ export function registerRoomInspectionTools(server) {
|
|
|
29
31
|
}
|
|
30
32
|
export async function getCurrentRoomPayload(conversationId) {
|
|
31
33
|
const runtime = requireValidWorkerBearerRuntime();
|
|
34
|
+
const daemonContext = getDaemonToolExecutionContext();
|
|
32
35
|
const publicCurrentRoom = toPublicCurrentRoomState();
|
|
33
36
|
const workerAuth = runtime.mode !== "owner"
|
|
34
37
|
? { source: runtime.mode === "worker" ? "worker_bearer" : "daemon_supervised", expires_at: null, account: null }
|
|
@@ -55,9 +58,11 @@ export async function getCurrentRoomPayload(conversationId) {
|
|
|
55
58
|
...publicCurrentRoom,
|
|
56
59
|
...(runtime.mode === "supervised" ? { room_binding: "daemon_supervised" } : {}),
|
|
57
60
|
...localCodexDetails,
|
|
58
|
-
agent_identity: toPublicAgentIdentity(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
agent_identity: toPublicAgentIdentity(daemonContext
|
|
62
|
+
? identityFromAgentSession(daemonContext.agentSession)
|
|
63
|
+
: getConversationIdentity(conversationId)
|
|
64
|
+
?? currentAgentIdentity
|
|
65
|
+
?? getStoredAgentIdentity(currentAgentIdentityKey)),
|
|
61
66
|
auth: workerAuth ?? (auth
|
|
62
67
|
? {
|
|
63
68
|
source: process.env.LETAGENTS_TOKEN ? "env" : "local_state",
|
|
@@ -73,7 +78,7 @@ export async function getCurrentRoomPayload(conversationId) {
|
|
|
73
78
|
}
|
|
74
79
|
function getRepoInspectionPayload(targetDir) {
|
|
75
80
|
const runtime = requireValidWorkerBearerRuntime();
|
|
76
|
-
const startDir = targetDir ||
|
|
81
|
+
const startDir = targetDir || getRuntimeWorkingDirectory();
|
|
77
82
|
const repoRoot = resolveGitRoot(startDir);
|
|
78
83
|
const configDir = repoRoot ? findExistingConfig(startDir) : null;
|
|
79
84
|
const configPath = configDir ? join(configDir, ".letagents.json") : null;
|
|
@@ -86,8 +91,8 @@ function getRepoInspectionPayload(targetDir) {
|
|
|
86
91
|
defaultBranch: repoRoot ? getGitDefaultBranch(repoRoot) : null,
|
|
87
92
|
})
|
|
88
93
|
: null;
|
|
89
|
-
const detectedRoom = configGitContext?.
|
|
90
|
-
const currentRoomMatchesContext =
|
|
94
|
+
const detectedRoom = configGitContext?.activeRoomLocator ?? gitContext?.activeRoomLocator ?? null;
|
|
95
|
+
const currentRoomMatchesContext = currentRoomMatchesLocator(detectedRoom);
|
|
91
96
|
return {
|
|
92
97
|
cwd: startDir,
|
|
93
98
|
repo_context_status: repoRoot ? "git_repo_detected" : "not_inside_git_repo",
|
|
@@ -95,10 +100,10 @@ function getRepoInspectionPayload(targetDir) {
|
|
|
95
100
|
config_file: configPath ?? null,
|
|
96
101
|
config_contents: readConfigContents(configPath),
|
|
97
102
|
configured_room_from_file: configuredRoom ?? null,
|
|
98
|
-
configured_active_room_from_context: configGitContext?.
|
|
99
|
-
derived_room_from_git: gitContext?.
|
|
103
|
+
configured_active_room_from_context: configGitContext?.activeRoomLocator ?? null,
|
|
104
|
+
derived_room_from_git: gitContext?.activeRoomLocator ?? null,
|
|
100
105
|
derived_repo_room_from_git: gitContext?.repoRoom ?? null,
|
|
101
|
-
derived_branch_room_from_git: gitContext?.
|
|
106
|
+
derived_branch_room_from_git: gitContext?.activeRefRoomLocator ?? null,
|
|
102
107
|
git_current_branch: gitContext?.currentBranch ?? configGitContext?.currentBranch ?? null,
|
|
103
108
|
git_default_branch: gitContext?.defaultBranch ?? configGitContext?.defaultBranch ?? null,
|
|
104
109
|
detected_room_from_context: detectedRoom ?? null,
|