letagents 0.12.11 → 0.12.13
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 +78 -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 +25 -12
- package/dist/mcp/server/runtime/agent-sessions.js +58 -9
- package/dist/mcp/server/runtime/api.js +40 -4
- package/dist/mcp/server/runtime/daemon-tool-context.js +11 -0
- package/dist/mcp/server/runtime/execution-profile.js +19 -0
- package/dist/mcp/server/runtime/identity/directory.js +2 -2
- package/dist/mcp/server/runtime/messages.js +55 -0
- package/dist/mcp/server/runtime/presence.js +4 -2
- package/dist/mcp/server/runtime/room-api.js +24 -7
- package/dist/mcp/server/runtime/room-state.js +59 -3
- package/dist/mcp/server/runtime/rooms.js +67 -32
- package/dist/mcp/server/runtime/supervised-room-authority.js +8 -0
- package/dist/mcp/server/runtime/supervisor-bridge.js +702 -24
- package/dist/mcp/server/runtime/tool-surface-policy.js +26 -0
- package/dist/mcp/server/runtime/worker-bearer.js +44 -6
- package/dist/mcp/server/runtime-contract.js +27 -0
- package/dist/mcp/server/runtime.js +15 -4
- package/dist/mcp/server/supervised-tool-facade.js +134 -0
- package/dist/mcp/server/tools/agent-sessions.js +74 -7
- package/dist/mcp/server/tools/messages/index.js +3 -2
- 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 +5 -0
- package/dist/mcp/server/tools/messages/status-tool.js +2 -0
- package/dist/mcp/server/tools/messages/wait-tool.js +344 -71
- package/dist/mcp/server/tools/onboarding/status-tool.js +9 -8
- package/dist/mcp/server/tools/rooms/inspection-tools.js +40 -22
- package/dist/mcp/server/tools/rooms/repo-initialization-tool.js +2 -1
- package/dist/mcp/server/tools/supervised-room-turn.js +42 -0
- package/dist/mcp/server/tools/tasks/board-tools.js +34 -2
- package/dist/mcp/server.js +14 -7
- package/dist/mcp/sse-client.js +163 -20
- package/dist/shared/activation-routing.js +187 -20
- 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 +11 -3
- package/shared/message-contracts.d.mts +32 -0
- package/shared/message-contracts.mjs +109 -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,30 +1,15 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { encodeRoomIdPath } from "../../../room-id.js";
|
|
3
|
-
import { appendIncludePromptOnly, currentRoom, ensureAgentIdentity, getFallbackProjectId, getLatestLocalChatMessages,
|
|
3
|
+
import { AGENT_MESSAGE_BODY_MAX_BYTES, appendIncludePromptOnly, boundAgentMessageOutput, buildAgentDeliveryHeaders, currentRoom, ensureAgentIdentity, getFallbackProjectId, getLatestLocalChatMessages, getCurrentAgentSessionSnapshot, getTargetRoomId, heartbeatRoomPresence, touchRoomSession, isLocalRoomStorageEnabled, resolveLocalRoomStorageIdentifiers, roomScopedApiCall, toAgentReadableMessages, } from "../../runtime.js";
|
|
4
|
+
import { requireValidWorkerBearerRuntime } from "../../runtime/worker-bearer.js";
|
|
4
5
|
import { jsonToolResponse } from "./response.js";
|
|
5
6
|
export const DEFAULT_READ_MESSAGES_LIMIT = 100;
|
|
6
7
|
// Both the API and the local store clamp a single page to 500 messages.
|
|
7
|
-
const MAX_MESSAGES_PER_PAGE = 500;
|
|
8
|
-
export function selectRecentMessages(messages, limit) {
|
|
9
|
-
const total = messages.length;
|
|
10
|
-
const selected = limit > 0 && total > limit ? messages.slice(total - limit) : messages;
|
|
11
|
-
return {
|
|
12
|
-
messages: selected,
|
|
13
|
-
total_message_count: total,
|
|
14
|
-
omitted_message_count: total - selected.length,
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
function withRecencyTelemetry(output, selection) {
|
|
18
|
-
output.total_message_count = selection.total_message_count;
|
|
19
|
-
if (selection.omitted_message_count > 0) {
|
|
20
|
-
output.omitted_message_count = selection.omitted_message_count;
|
|
21
|
-
output.truncated = true;
|
|
22
|
-
}
|
|
23
|
-
return output;
|
|
24
|
-
}
|
|
8
|
+
export const MAX_MESSAGES_PER_PAGE = 500;
|
|
25
9
|
// Fetch the most recent messages by paging BACKWARDS from the tail
|
|
26
10
|
// (before=latest), so a limited read never walks the full room history.
|
|
27
11
|
export async function fetchRecentRemoteMessages(input) {
|
|
12
|
+
const boundedLimit = Math.max(1, Math.min(MAX_MESSAGES_PER_PAGE, Math.floor(input.limit)));
|
|
28
13
|
const collected = [];
|
|
29
14
|
let beforeCursor = "latest";
|
|
30
15
|
let truncated = false;
|
|
@@ -33,7 +18,7 @@ export async function fetchRecentRemoteMessages(input) {
|
|
|
33
18
|
for (;;) {
|
|
34
19
|
const params = new URLSearchParams();
|
|
35
20
|
params.set("before", beforeCursor);
|
|
36
|
-
params.set("limit", String(Math.min(
|
|
21
|
+
params.set("limit", String(Math.min(boundedLimit - collected.length, MAX_MESSAGES_PER_PAGE)));
|
|
37
22
|
const qs = params.toString();
|
|
38
23
|
const result = await roomScopedApiCall({
|
|
39
24
|
room_id: input.targetRoomId,
|
|
@@ -44,6 +29,7 @@ export async function fetchRecentRemoteMessages(input) {
|
|
|
44
29
|
// page touch the session would walk last_message_id backwards and make
|
|
45
30
|
// resume_room_session replay already-read history.
|
|
46
31
|
preserve_session_cursor: true,
|
|
32
|
+
options: { headers: input.deliveryHeaders },
|
|
47
33
|
});
|
|
48
34
|
roomIdFromResponse = roomIdFromResponse || result.room_id || result.project_id;
|
|
49
35
|
const msgs = result.messages ?? [];
|
|
@@ -56,7 +42,7 @@ export async function fetchRecentRemoteMessages(input) {
|
|
|
56
42
|
const hasOlder = Boolean(result.has_older ?? result.has_more);
|
|
57
43
|
if (!hasOlder || msgs.length === 0)
|
|
58
44
|
break;
|
|
59
|
-
if (collected.length >=
|
|
45
|
+
if (collected.length >= boundedLimit) {
|
|
60
46
|
truncated = true;
|
|
61
47
|
break;
|
|
62
48
|
}
|
|
@@ -73,103 +59,74 @@ export async function fetchRecentRemoteMessages(input) {
|
|
|
73
59
|
return { messages: collected, truncated, roomIdFromResponse };
|
|
74
60
|
}
|
|
75
61
|
export function registerReadMessagesTool(server) {
|
|
76
|
-
server.tool("read_messages", "Read recent
|
|
62
|
+
server.tool("read_messages", "Read a bounded recent page from a Let Agents Chat room (most recent `limit`, default 100, maximum 500). Threaded replies include thread_parent_id/thread.root_message_id; use send_thread_message with that id to continue focused side discussion without polluting the main room. For long-running work, prefer wait_for_messages with after_message_id so you only process new lines and do not treat an empty poll as the end of the mission.", {
|
|
77
63
|
room_id: z.string().optional().describe("Canonical room ID. Defaults to the current room."),
|
|
78
64
|
limit: z
|
|
79
65
|
.number()
|
|
80
66
|
.int()
|
|
81
67
|
.min(0)
|
|
82
68
|
.optional()
|
|
83
|
-
.describe(`Return
|
|
69
|
+
.describe(`Return the most recent N messages (default ${DEFAULT_READ_MESSAGES_LIMIT}, maximum ${MAX_MESSAGES_PER_PAGE}). Zero is retained for compatibility and means the bounded maximum. When older messages are omitted, the response carries truncated=true.`),
|
|
84
70
|
}, async ({ room_id, limit }) => {
|
|
85
|
-
const
|
|
71
|
+
const requestedLimit = limit ?? DEFAULT_READ_MESSAGES_LIMIT;
|
|
72
|
+
const effectiveLimit = requestedLimit > 0
|
|
73
|
+
? Math.min(requestedLimit, MAX_MESSAGES_PER_PAGE)
|
|
74
|
+
: MAX_MESSAGES_PER_PAGE;
|
|
86
75
|
const targetRoomId = getTargetRoomId(room_id);
|
|
87
76
|
const targetProjectId = getFallbackProjectId();
|
|
88
77
|
const localRoomId = targetRoomId ?? currentRoom?.room_id ?? targetProjectId;
|
|
78
|
+
const sessionRoomId = targetRoomId ?? currentRoom?.room_id ?? null;
|
|
79
|
+
const workerRuntime = requireValidWorkerBearerRuntime();
|
|
80
|
+
const agentSessionSnapshot = workerRuntime.mode === "owner"
|
|
81
|
+
? getCurrentAgentSessionSnapshot(sessionRoomId)
|
|
82
|
+
: { complete: true, session: null };
|
|
83
|
+
const agentSession = agentSessionSnapshot.session;
|
|
84
|
+
const deliveryHeaders = buildAgentDeliveryHeaders(agentSession);
|
|
85
|
+
if (!agentSessionSnapshot.complete) {
|
|
86
|
+
throw new Error("Local agent routing state is unavailable; retry after restoring the state file.");
|
|
87
|
+
}
|
|
89
88
|
if (localRoomId && await isLocalRoomStorageEnabled(localRoomId)) {
|
|
90
|
-
const { localRoomId: sqliteRoomId } = await resolveLocalRoomStorageIdentifiers(localRoomId);
|
|
89
|
+
const { localRoomId: sqliteRoomId, cloudRoomId, } = await resolveLocalRoomStorageIdentifiers(localRoomId);
|
|
91
90
|
const effectiveLocalRoomId = sqliteRoomId || localRoomId;
|
|
92
|
-
|
|
93
|
-
const result = await getLatestLocalChatMessages(effectiveLocalRoomId, {
|
|
94
|
-
limit: effectiveLimit,
|
|
95
|
-
include_prompt_only: true,
|
|
96
|
-
});
|
|
97
|
-
const output = {
|
|
98
|
-
room_id: effectiveLocalRoomId,
|
|
99
|
-
messages: toAgentReadableMessages(result.messages ?? []),
|
|
100
|
-
};
|
|
101
|
-
if (result.has_more)
|
|
102
|
-
output.truncated = true;
|
|
103
|
-
return jsonToolResponse(output);
|
|
104
|
-
}
|
|
105
|
-
const allMessages = [];
|
|
106
|
-
let afterCursor;
|
|
107
|
-
for (;;) {
|
|
108
|
-
const result = await getLocalChatMessages(effectiveLocalRoomId, {
|
|
109
|
-
after: afterCursor,
|
|
110
|
-
include_prompt_only: true,
|
|
111
|
-
});
|
|
112
|
-
const msgs = result.messages ?? [];
|
|
113
|
-
allMessages.push(...msgs);
|
|
114
|
-
if (!result.has_more || msgs.length === 0)
|
|
115
|
-
break;
|
|
116
|
-
const lastMsg = msgs[msgs.length - 1];
|
|
117
|
-
if (!lastMsg?.id)
|
|
118
|
-
break;
|
|
119
|
-
afterCursor = lastMsg.id;
|
|
120
|
-
}
|
|
121
|
-
const selection = selectRecentMessages(allMessages, effectiveLimit);
|
|
122
|
-
return jsonToolResponse(withRecencyTelemetry({
|
|
123
|
-
room_id: effectiveLocalRoomId,
|
|
124
|
-
messages: toAgentReadableMessages(selection.messages),
|
|
125
|
-
}, selection));
|
|
126
|
-
}
|
|
127
|
-
if (effectiveLimit > 0) {
|
|
128
|
-
const recent = await fetchRecentRemoteMessages({
|
|
129
|
-
targetRoomId,
|
|
130
|
-
targetProjectId,
|
|
91
|
+
const result = await getLatestLocalChatMessages(effectiveLocalRoomId, {
|
|
131
92
|
limit: effectiveLimit,
|
|
93
|
+
include_prompt_only: true,
|
|
94
|
+
});
|
|
95
|
+
const { attachLocalActivationMetadata } = await import("./wait-tool.js");
|
|
96
|
+
const messages = await attachLocalActivationMetadata(effectiveLocalRoomId, result.messages ?? [], agentSession, {
|
|
97
|
+
includeTaskOwnerLeases: false,
|
|
98
|
+
activeSessionRoomId: cloudRoomId || sessionRoomId,
|
|
132
99
|
});
|
|
133
|
-
|
|
100
|
+
const bounded = boundAgentMessageOutput(toAgentReadableMessages(messages), { direction: "suffix", maxBytes: AGENT_MESSAGE_BODY_MAX_BYTES });
|
|
134
101
|
const output = {
|
|
135
|
-
|
|
102
|
+
room_id: effectiveLocalRoomId,
|
|
103
|
+
messages: bounded.messages,
|
|
136
104
|
};
|
|
137
|
-
if (
|
|
105
|
+
if (result.has_more || bounded.truncated)
|
|
138
106
|
output.truncated = true;
|
|
139
|
-
if (
|
|
140
|
-
output
|
|
107
|
+
if (bounded.omittedMessageCount > 0) {
|
|
108
|
+
output.omitted_message_count = bounded.omittedMessageCount;
|
|
141
109
|
}
|
|
142
110
|
return jsonToolResponse(output);
|
|
143
111
|
}
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
query.set("after", afterCursor);
|
|
151
|
-
const qs = query.toString();
|
|
152
|
-
const result = await roomScopedApiCall({
|
|
153
|
-
room_id: targetRoomId,
|
|
154
|
-
project_id: targetProjectId,
|
|
155
|
-
room_path: (targetRoomId) => appendIncludePromptOnly(`/rooms/${encodeRoomIdPath(targetRoomId)}/messages${qs ? `?${qs}` : ""}`),
|
|
156
|
-
project_path: (targetProjectId) => appendIncludePromptOnly(`/projects/${encodeURIComponent(targetProjectId)}/messages${qs ? `?${qs}` : ""}`),
|
|
157
|
-
});
|
|
158
|
-
roomIdFromResponse = roomIdFromResponse || result.room_id || result.project_id;
|
|
159
|
-
const msgs = result.messages ?? [];
|
|
160
|
-
allMessages.push(...msgs);
|
|
161
|
-
if (!result.has_more || msgs.length === 0)
|
|
162
|
-
break;
|
|
163
|
-
const lastMsg = msgs[msgs.length - 1];
|
|
164
|
-
if (!lastMsg?.id)
|
|
165
|
-
break;
|
|
166
|
-
afterCursor = lastMsg.id;
|
|
167
|
-
}
|
|
112
|
+
const recent = await fetchRecentRemoteMessages({
|
|
113
|
+
targetRoomId,
|
|
114
|
+
targetProjectId,
|
|
115
|
+
limit: effectiveLimit,
|
|
116
|
+
deliveryHeaders,
|
|
117
|
+
});
|
|
168
118
|
await heartbeatRoomPresence(targetRoomId ?? currentRoom?.room_id ?? null, await ensureAgentIdentity());
|
|
169
|
-
const
|
|
170
|
-
const output =
|
|
171
|
-
|
|
172
|
-
|
|
119
|
+
const bounded = boundAgentMessageOutput(toAgentReadableMessages(recent.messages), { direction: "suffix", maxBytes: AGENT_MESSAGE_BODY_MAX_BYTES });
|
|
120
|
+
const output = {
|
|
121
|
+
messages: bounded.messages,
|
|
122
|
+
};
|
|
123
|
+
if (recent.truncated || bounded.truncated)
|
|
124
|
+
output.truncated = true;
|
|
125
|
+
if (bounded.omittedMessageCount > 0) {
|
|
126
|
+
output.omitted_message_count = bounded.omittedMessageCount;
|
|
127
|
+
}
|
|
128
|
+
if (recent.roomIdFromResponse) {
|
|
129
|
+
output[targetRoomId ? "room_id" : "project_id"] = recent.roomIdFromResponse;
|
|
173
130
|
}
|
|
174
131
|
return jsonToolResponse(output);
|
|
175
132
|
});
|
|
@@ -61,6 +61,8 @@ export function registerPostReasoningTool(server) {
|
|
|
61
61
|
sender: identity.actor_label,
|
|
62
62
|
text: normalizedMilestone,
|
|
63
63
|
source: "agent",
|
|
64
|
+
publisher_agent_key: agentSession?.agent_key ?? null,
|
|
65
|
+
publisher_agent_session_id: agentSession?.session_id ?? null,
|
|
64
66
|
});
|
|
65
67
|
milestoneMessageId = milestoneMessage.id;
|
|
66
68
|
touchCurrentRoom(milestoneMessageId);
|
|
@@ -87,6 +87,8 @@ async function sendMessageFromTool(input) {
|
|
|
87
87
|
reply_to: replyTarget,
|
|
88
88
|
...(resolvedThreadRoot ? { thread_root_id: resolvedThreadRoot } : {}),
|
|
89
89
|
source: "agent",
|
|
90
|
+
publisher_agent_key: agentSession?.agent_key ?? null,
|
|
91
|
+
publisher_agent_session_id: agentSession?.session_id ?? null,
|
|
90
92
|
});
|
|
91
93
|
touchCurrentRoom(message.id);
|
|
92
94
|
return jsonToolResponse({
|
|
@@ -112,6 +114,9 @@ async function sendMessageFromTool(input) {
|
|
|
112
114
|
});
|
|
113
115
|
touchCurrentRoom(typeof message.id === "string" ? message.id : undefined);
|
|
114
116
|
await syncRoomPresence(targetRoomId ?? currentRoom?.room_id ?? null, identity, getRememberedRoomPresence(targetRoomId ?? currentRoom?.room_id ?? null, identity), agentSession);
|
|
117
|
+
// The `replied` receipt transition is server-owned: message creation marks
|
|
118
|
+
// the publisher's receipt on the reply target atomically with the reply
|
|
119
|
+
// itself, for MCP workers and supervised daemon publications alike.
|
|
115
120
|
return jsonToolResponse({
|
|
116
121
|
...message,
|
|
117
122
|
agent_identity: toPublicAgentIdentity(identity),
|
|
@@ -45,6 +45,8 @@ export function registerPostStatusTool(server) {
|
|
|
45
45
|
sender,
|
|
46
46
|
text: statusText,
|
|
47
47
|
source: "agent",
|
|
48
|
+
publisher_agent_key: agentSession?.agent_key ?? null,
|
|
49
|
+
publisher_agent_session_id: agentSession?.session_id ?? null,
|
|
48
50
|
});
|
|
49
51
|
touchCurrentRoom(message.id);
|
|
50
52
|
return jsonToolResponse({
|