letagents 0.12.9 → 0.12.11
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/README.md +18 -15
- package/dist/api/board-intent-payloads.js +25 -0
- package/dist/mcp/agent-prompt-delivery.js +20 -0
- package/dist/mcp/codex-session/app-server.js +89 -0
- package/dist/mcp/codex-session/index.js +10 -0
- package/dist/mcp/codex-session/rpc-client.js +85 -0
- package/dist/mcp/codex-session/runtime-bridge.js +271 -0
- package/dist/mcp/codex-session/runtime-summary.js +220 -0
- package/dist/mcp/codex-session/session-inspection.js +100 -0
- package/dist/mcp/codex-session/session-mapper.js +51 -0
- package/dist/mcp/codex-session/session-start.js +182 -0
- package/dist/mcp/codex-session/session-status.js +77 -0
- package/dist/mcp/codex-session/session-stop.js +43 -0
- package/dist/mcp/codex-session/session-supervisor.js +111 -0
- package/dist/mcp/codex-session/start-prompt.js +49 -0
- package/dist/mcp/codex-session/types.js +1 -0
- package/dist/mcp/codex-session.js +1 -672
- package/dist/mcp/config-reader.js +10 -2
- package/dist/mcp/git-remote.js +90 -14
- package/dist/mcp/local-state/agent-identity.js +26 -0
- package/dist/mcp/local-state/agent-sessions.js +66 -0
- package/dist/mcp/local-state/auth.js +57 -0
- package/dist/mcp/local-state/codex-live-sessions.js +59 -0
- package/dist/mcp/local-state/index.js +8 -0
- package/dist/mcp/local-state/local-chat.js +808 -0
- package/dist/mcp/local-state/room-sessions.js +51 -0
- package/dist/mcp/local-state/storage.js +95 -0
- package/dist/mcp/local-state/types.js +1 -0
- package/dist/mcp/local-state.js +1 -351
- package/dist/mcp/managed-agent-providers.js +65 -0
- package/dist/mcp/rental-tools/activity.js +37 -0
- package/dist/mcp/rental-tools/budget.js +36 -0
- package/dist/mcp/rental-tools/context.js +92 -0
- package/dist/mcp/rental-tools/lifecycle.js +47 -0
- package/dist/mcp/rental-tools/metering.js +73 -0
- package/dist/mcp/rental-tools/patches.js +100 -0
- package/dist/mcp/rental-tools/provider.js +117 -0
- package/dist/mcp/rental-tools/shared.js +19 -0
- package/dist/mcp/rental-tools/types.js +1 -0
- package/dist/mcp/rental-tools.js +7 -0
- package/dist/mcp/repo-visibility.js +8 -6
- package/dist/mcp/server/register-tools.js +19 -0
- package/dist/mcp/server/repo-context.js +28 -0
- package/dist/mcp/server/resources.js +77 -0
- package/dist/mcp/server/runtime/agent-sessions.js +173 -0
- package/dist/mcp/server/runtime/api.js +104 -0
- package/dist/mcp/server/runtime/auth-cache.js +20 -0
- package/dist/mcp/server/runtime/device-auth.js +48 -0
- package/dist/mcp/server/runtime/identity/config.js +58 -0
- package/dist/mcp/server/runtime/identity/directory.js +94 -0
- package/dist/mcp/server/runtime/identity/liveness.js +32 -0
- package/dist/mcp/server/runtime/identity/names.js +74 -0
- package/dist/mcp/server/runtime/identity/public.js +19 -0
- package/dist/mcp/server/runtime/identity/state.js +32 -0
- package/dist/mcp/server/runtime/identity.js +97 -0
- package/dist/mcp/server/runtime/messages.js +160 -0
- package/dist/mcp/server/runtime/presence.js +52 -0
- package/dist/mcp/server/runtime/room-api.js +42 -0
- package/dist/mcp/server/runtime/room-state.js +122 -0
- package/dist/mcp/server/runtime/rooms.js +374 -0
- package/dist/mcp/server/runtime/supervisor-bridge.js +94 -0
- package/dist/mcp/server/runtime/worker-bearer.js +67 -0
- package/dist/mcp/server/runtime.js +14 -0
- package/dist/mcp/server/tools/agent-sessions.js +415 -0
- package/dist/mcp/server/tools/messages/index.js +14 -0
- package/dist/mcp/server/tools/messages/message-lookup.js +75 -0
- package/dist/mcp/server/tools/messages/read-tool.js +176 -0
- package/dist/mcp/server/tools/messages/reasoning-tool.js +179 -0
- package/dist/mcp/server/tools/messages/response.js +17 -0
- package/dist/mcp/server/tools/messages/send-tool.js +160 -0
- package/dist/mcp/server/tools/messages/status-tool.js +87 -0
- package/dist/mcp/server/tools/messages/wait-tool.js +403 -0
- package/dist/mcp/server/tools/messages.js +1 -0
- package/dist/mcp/server/tools/onboarding/device-auth-tools.js +158 -0
- package/dist/mcp/server/tools/onboarding/name-tool.js +93 -0
- package/dist/mcp/server/tools/onboarding/responses.js +10 -0
- package/dist/mcp/server/tools/onboarding/status-tool.js +125 -0
- package/dist/mcp/server/tools/onboarding.js +8 -0
- package/dist/mcp/server/tools/rental/activity-tools.js +43 -0
- package/dist/mcp/server/tools/rental/context-tools.js +43 -0
- package/dist/mcp/server/tools/rental/index.js +17 -0
- package/dist/mcp/server/tools/rental/metering-tools.js +39 -0
- package/dist/mcp/server/tools/rental/patch-tools.js +53 -0
- package/dist/mcp/server/tools/rental/provider-tools.js +44 -0
- package/dist/mcp/server/tools/rental/response.js +10 -0
- package/dist/mcp/server/tools/rental/types.js +1 -0
- package/dist/mcp/server/tools/rental.js +1 -0
- package/dist/mcp/server/tools/rooms/index.js +5 -0
- package/dist/mcp/server/tools/rooms/inspection-tools.js +140 -0
- package/dist/mcp/server/tools/rooms/join-tools.js +37 -0
- package/dist/mcp/server/tools/rooms/repo-initialization-tool.js +113 -0
- package/dist/mcp/server/tools/rooms/repo-visibility-tool.js +20 -0
- package/dist/mcp/server/tools/rooms/response.js +10 -0
- package/dist/mcp/server/tools/rooms/resume-tool.js +48 -0
- package/dist/mcp/server/tools/rooms.js +1 -0
- package/dist/mcp/server/tools/tasks/api.js +222 -0
- package/dist/mcp/server/tools/tasks/artifact-tools.js +75 -0
- package/dist/mcp/server/tools/tasks/board-intent-tools.js +326 -0
- package/dist/mcp/server/tools/tasks/board-tools.js +73 -0
- package/dist/mcp/server/tools/tasks/context.js +35 -0
- package/dist/mcp/server/tools/tasks/event-tools.js +40 -0
- package/dist/mcp/server/tools/tasks/index.js +16 -0
- package/dist/mcp/server/tools/tasks/lease-tools.js +145 -0
- package/dist/mcp/server/tools/tasks/mutation-tools.js +121 -0
- package/dist/mcp/server/tools/tasks/response.js +13 -0
- package/dist/mcp/server/tools/tasks/schemas.js +68 -0
- package/dist/mcp/server/tools/tasks/verdict-tools.js +51 -0
- package/dist/mcp/server/tools/tasks.js +1 -0
- package/dist/mcp/server.js +12 -3157
- package/dist/mcp/sse-client.js +9 -17
- package/dist/shared/activation-routing.js +272 -0
- package/dist/shared/agent-identity.js +21 -5
- package/dist/shared/agent-presence.js +5 -3
- package/dist/shared/agent-session-bearer.js +28 -0
- package/dist/shared/board-manager-failover.js +16 -0
- package/dist/shared/rental/lrt.js +214 -0
- package/dist/shared/rental/meter-types.js +14 -0
- package/dist/shared/room-agent-prompts.js +14 -1
- package/package.json +31 -16
- package/dist/mcp/__tests__/config-reader.test.js +0 -93
- package/dist/mcp/__tests__/git-remote.test.js +0 -37
- package/dist/mcp/__tests__/server-helpers.test.js +0 -121
- package/dist/shared/agent-reasoning.js +0 -49
- /package/dist/{mcp → shared}/codenames.js +0 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getPollTimeoutCapMs } from "../../../../shared/poll-timeout-cap.js";
|
|
3
|
+
import { encodeRoomIdPath } from "../../../room-id.js";
|
|
4
|
+
import { appendIncludePromptOnly, buildAgentDeliveryHeaders, currentRoom, ensureAgentIdentity, getFallbackProjectId, getLatestLocalChatMessages, getLocalChatMessages, getLastMessageId, getRememberedRoomPresence, getTargetRoomId, identityFromAgentSession, isLocalRoomStorageEnabled, listLocalTasks, resolveLocalRoomStorageIdentifiers, resolveAgentSession, roomScopedApiCall, syncRoomPresence, toAgentReadableMessages, touchRoomSession, WORKER_BEARER_AGENT_SESSION_ID, waitForLocalChatMessages, } from "../../runtime.js";
|
|
5
|
+
import { requireValidWorkerBearerRuntime } from "../../runtime/worker-bearer.js";
|
|
6
|
+
import { attachAgentMessageActivations } from "../../../../shared/activation-routing.js";
|
|
7
|
+
import { findLocalMessageById, findRemoteMessageById } from "./message-lookup.js";
|
|
8
|
+
import { fetchRecentRemoteMessages } from "./read-tool.js";
|
|
9
|
+
import { jsonToolResponse } from "./response.js";
|
|
10
|
+
const DEFAULT_POLL_TIMEOUT_MS = 30000;
|
|
11
|
+
// When wait_for_messages is called WITHOUT an after_message_id cursor, it must
|
|
12
|
+
// catch up on only the most RECENT messages instead of replaying the entire
|
|
13
|
+
// room history (busy rooms archive millions of characters, which blows the
|
|
14
|
+
// tool's token budget). This bounds that no-cursor catch-up to a recent tail.
|
|
15
|
+
export const DEFAULT_WAIT_CATCHUP_LIMIT = 100;
|
|
16
|
+
// Decide how the initial fetch should behave. With a cursor we return
|
|
17
|
+
// everything after it (genuine "new since I last polled", already bounded);
|
|
18
|
+
// without one we fetch only the bounded recent tail.
|
|
19
|
+
export function planWaitForMessagesFetch(input) {
|
|
20
|
+
if (input.effectiveAfterMessageId) {
|
|
21
|
+
return { mode: "after_cursor", after: input.effectiveAfterMessageId };
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
mode: "catch_up_tail",
|
|
25
|
+
limit: input.catchupLimit ?? DEFAULT_WAIT_CATCHUP_LIMIT,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const LOCAL_TASK_OWNER_STATUSES = new Set(["assigned", "in_progress", "blocked", "in_review"]);
|
|
29
|
+
export function buildWaitForMessagesRequestOptions(input) {
|
|
30
|
+
return {
|
|
31
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
32
|
+
headers: input.deliveryHeaders,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
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
|
+
function isRecord(value) {
|
|
46
|
+
return Boolean(value && typeof value === "object");
|
|
47
|
+
}
|
|
48
|
+
function messageId(message) {
|
|
49
|
+
return typeof message.id === "string" && message.id.trim() ? message.id : null;
|
|
50
|
+
}
|
|
51
|
+
export function resolveEffectiveAfterMessageId(input) {
|
|
52
|
+
const requested = input.requestedAfterMessageId?.trim();
|
|
53
|
+
return requested || undefined;
|
|
54
|
+
}
|
|
55
|
+
function replyReferenceId(message) {
|
|
56
|
+
const reply = message.reply_to ?? message.replyTo ?? null;
|
|
57
|
+
if (typeof reply === "string" && reply.trim()) {
|
|
58
|
+
return reply;
|
|
59
|
+
}
|
|
60
|
+
if (isRecord(reply) && typeof reply.id === "string" && reply.id.trim()) {
|
|
61
|
+
return reply.id;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
function activationDecision(message) {
|
|
66
|
+
if (!isRecord(message))
|
|
67
|
+
return null;
|
|
68
|
+
const activation = message.activation;
|
|
69
|
+
if (!isRecord(activation))
|
|
70
|
+
return null;
|
|
71
|
+
const forCurrentAgent = activation.for_current_agent;
|
|
72
|
+
if (!isRecord(forCurrentAgent))
|
|
73
|
+
return null;
|
|
74
|
+
return typeof forCurrentAgent.decision === "string" ? forCurrentAgent.decision : null;
|
|
75
|
+
}
|
|
76
|
+
export function filterSilentActivationMessages(messages) {
|
|
77
|
+
const visibleMessages = [];
|
|
78
|
+
const skippedMessageIds = [];
|
|
79
|
+
let lastObservedMessageId = null;
|
|
80
|
+
for (const message of messages) {
|
|
81
|
+
if (isRecord(message)) {
|
|
82
|
+
lastObservedMessageId = messageId(message) ?? lastObservedMessageId;
|
|
83
|
+
}
|
|
84
|
+
if (activationDecision(message) === "silent") {
|
|
85
|
+
if (isRecord(message)) {
|
|
86
|
+
const id = messageId(message);
|
|
87
|
+
if (id)
|
|
88
|
+
skippedMessageIds.push(id);
|
|
89
|
+
}
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
visibleMessages.push(message);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
messages: visibleMessages,
|
|
96
|
+
skipped_message_ids: skippedMessageIds,
|
|
97
|
+
last_observed_message_id: lastObservedMessageId,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function addActivationRoutingTelemetry(output, routing) {
|
|
101
|
+
if (routing.last_observed_message_id) {
|
|
102
|
+
output.last_observed_message_id = routing.last_observed_message_id;
|
|
103
|
+
}
|
|
104
|
+
if (routing.skipped_message_ids.length > 0) {
|
|
105
|
+
output.skipped_message_ids = routing.skipped_message_ids;
|
|
106
|
+
output.skipped_message_count = routing.skipped_message_ids.length;
|
|
107
|
+
}
|
|
108
|
+
return output;
|
|
109
|
+
}
|
|
110
|
+
export function resolveWaitAgentSession(roomId, agentSessionId) {
|
|
111
|
+
if (requireValidWorkerBearerRuntime().mode === "worker" &&
|
|
112
|
+
(!agentSessionId?.trim() || agentSessionId === WORKER_BEARER_AGENT_SESSION_ID)) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
if (!agentSessionId?.trim()) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return resolveAgentSession(roomId, agentSessionId);
|
|
119
|
+
}
|
|
120
|
+
export async function localActivationContext(roomId) {
|
|
121
|
+
const result = await listLocalTasks(roomId, { openOnly: true });
|
|
122
|
+
return {
|
|
123
|
+
activeTaskLeases: result.tasks
|
|
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
|
+
}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
async function attachLocalActivationMetadata(roomId, messages, agentSession, options = {}) {
|
|
141
|
+
if (!agentSession || agentSession.session_kind !== "worker") {
|
|
142
|
+
return messages;
|
|
143
|
+
}
|
|
144
|
+
const records = messages.filter(isRecord);
|
|
145
|
+
const activationContext = options.includeTaskOwnerLeases === false
|
|
146
|
+
? undefined
|
|
147
|
+
: await localActivationContext(roomId);
|
|
148
|
+
return attachAgentMessageActivations(records, {
|
|
149
|
+
actor_label: agentSession.actor_label,
|
|
150
|
+
agent_key: agentSession.agent_key,
|
|
151
|
+
agent_instance_id: agentSession.agent_instance_id ?? null,
|
|
152
|
+
agent_session_id: agentSession.session_id,
|
|
153
|
+
display_name: agentSession.display_name,
|
|
154
|
+
session_kind: agentSession.session_kind,
|
|
155
|
+
}, activationContext);
|
|
156
|
+
}
|
|
157
|
+
// Resolving an out-of-window parent costs a lookup (a by-id fetch, or a
|
|
158
|
+
// page-by-page history scan against older APIs), so resolved (and observed)
|
|
159
|
+
// messages are cached per process to keep repeat polls from re-resolving the
|
|
160
|
+
// same thread roots. Message ids are
|
|
161
|
+
// room-scoped, so the key includes the room scope. Message records are treated
|
|
162
|
+
// as immutable once posted; a bounded insertion-ordered map keeps memory flat
|
|
163
|
+
// for long-running workers.
|
|
164
|
+
const THREAD_CONTEXT_CACHE_MAX = 500;
|
|
165
|
+
const threadContextCache = new Map();
|
|
166
|
+
function threadContextCacheKey(scopeId, targetMessageId) {
|
|
167
|
+
return `${scopeId}:${targetMessageId}`;
|
|
168
|
+
}
|
|
169
|
+
export function rememberThreadContextMessages(scopeId, records) {
|
|
170
|
+
for (const record of records) {
|
|
171
|
+
if (!isRecord(record))
|
|
172
|
+
continue;
|
|
173
|
+
const id = messageId(record);
|
|
174
|
+
if (!id)
|
|
175
|
+
continue;
|
|
176
|
+
const key = threadContextCacheKey(scopeId, id);
|
|
177
|
+
threadContextCache.delete(key);
|
|
178
|
+
threadContextCache.set(key, record);
|
|
179
|
+
}
|
|
180
|
+
while (threadContextCache.size > THREAD_CONTEXT_CACHE_MAX) {
|
|
181
|
+
const oldest = threadContextCache.keys().next().value;
|
|
182
|
+
if (oldest === undefined)
|
|
183
|
+
break;
|
|
184
|
+
threadContextCache.delete(oldest);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
export function resetThreadContextCacheForTests() {
|
|
188
|
+
threadContextCache.clear();
|
|
189
|
+
}
|
|
190
|
+
export async function collectThreadContextMessages(input) {
|
|
191
|
+
const records = input.messages.filter(isRecord);
|
|
192
|
+
const knownIds = new Set(records.map(messageId).filter((id) => Boolean(id)));
|
|
193
|
+
const seenIds = new Set(knownIds);
|
|
194
|
+
const pendingIds = records
|
|
195
|
+
.map(replyReferenceId)
|
|
196
|
+
.filter((id) => Boolean(id && !knownIds.has(id)));
|
|
197
|
+
const scopeId = input.roomId ?? input.localRoomId ?? input.projectId ?? "";
|
|
198
|
+
rememberThreadContextMessages(scopeId, records);
|
|
199
|
+
if (pendingIds.length === 0) {
|
|
200
|
+
// Nothing quotes an out-of-window message (idle polls land here), so skip
|
|
201
|
+
// storage-mode resolution entirely.
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
const contextMessages = [];
|
|
205
|
+
const useLocalStorage = Boolean(input.localRoomId && await isLocalRoomStorageEnabled(input.localRoomId));
|
|
206
|
+
const localIdentifiers = useLocalStorage
|
|
207
|
+
? await resolveLocalRoomStorageIdentifiers(input.localRoomId)
|
|
208
|
+
: { localRoomId: input.localRoomId };
|
|
209
|
+
const sqliteRoomId = localIdentifiers.localRoomId || input.localRoomId;
|
|
210
|
+
while (pendingIds.length > 0) {
|
|
211
|
+
const nextId = pendingIds.shift();
|
|
212
|
+
if (!nextId || seenIds.has(nextId))
|
|
213
|
+
continue;
|
|
214
|
+
seenIds.add(nextId);
|
|
215
|
+
const cached = threadContextCache.get(threadContextCacheKey(scopeId, nextId));
|
|
216
|
+
const message = cached
|
|
217
|
+
?? (useLocalStorage && input.localRoomId
|
|
218
|
+
? await findLocalMessageById(sqliteRoomId || input.localRoomId, nextId)
|
|
219
|
+
: await findRemoteMessageById({
|
|
220
|
+
roomId: input.roomId,
|
|
221
|
+
projectId: input.projectId,
|
|
222
|
+
messageId: nextId,
|
|
223
|
+
}));
|
|
224
|
+
if (!message)
|
|
225
|
+
continue;
|
|
226
|
+
if (!cached) {
|
|
227
|
+
rememberThreadContextMessages(scopeId, [message]);
|
|
228
|
+
}
|
|
229
|
+
contextMessages.push(message);
|
|
230
|
+
const parentId = replyReferenceId(message);
|
|
231
|
+
if (parentId && !seenIds.has(parentId)) {
|
|
232
|
+
pendingIds.push(parentId);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return contextMessages;
|
|
236
|
+
}
|
|
237
|
+
export function registerWaitForMessagesTool(server) {
|
|
238
|
+
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'. If someone posted a premature 'I will wait' closing line, use send_message with a brief continue instruction. Per-call wait is capped (default max 180s unless LETAGENTS_POLL_MAX_MS is set on API and MCP).", {
|
|
239
|
+
room_id: z.string().optional().describe("Canonical room ID. Defaults to the current room."),
|
|
240
|
+
after_message_id: z
|
|
241
|
+
.string()
|
|
242
|
+
.optional()
|
|
243
|
+
.describe(`Only return messages after this message ID (e.g. 'msg_3'). If omitted, returns just the most recent ${DEFAULT_WAIT_CATCHUP_LIMIT} messages (bounded recent tail) instead of the full room history, and advances the session cursor to the newest message.`),
|
|
244
|
+
timeout: z
|
|
245
|
+
.number()
|
|
246
|
+
.optional()
|
|
247
|
+
.describe("Maximum wait time in milliseconds (min 1000, capped by LETAGENTS_POLL_MAX_MS / server). If set to 0, the default timeout will be used."),
|
|
248
|
+
agent_session_id: z
|
|
249
|
+
.string()
|
|
250
|
+
.optional()
|
|
251
|
+
.describe("Registered agent session to use. Without this, the MCP transport is treated as controller traffic and is hidden from connected-agent activity."),
|
|
252
|
+
}, async ({ room_id, after_message_id, timeout, agent_session_id }) => {
|
|
253
|
+
const targetRoomId = getTargetRoomId(room_id);
|
|
254
|
+
const targetProjectId = getFallbackProjectId();
|
|
255
|
+
const localRoomId = targetRoomId ?? currentRoom?.room_id ?? targetProjectId;
|
|
256
|
+
const identity = await ensureAgentIdentity();
|
|
257
|
+
const sessionRoomId = targetRoomId ?? currentRoom?.room_id ?? localRoomId ?? null;
|
|
258
|
+
const agentSession = resolveWaitAgentSession(sessionRoomId, agent_session_id);
|
|
259
|
+
const maxPollMs = getPollTimeoutCapMs();
|
|
260
|
+
const serverTimeout = Math.min(Math.max(timeout || DEFAULT_POLL_TIMEOUT_MS, 1000), maxPollMs);
|
|
261
|
+
if (localRoomId && await isLocalRoomStorageEnabled(localRoomId)) {
|
|
262
|
+
const { localRoomId: sqliteRoomId } = await resolveLocalRoomStorageIdentifiers(localRoomId);
|
|
263
|
+
const effectiveLocalRoomId = sqliteRoomId || localRoomId;
|
|
264
|
+
const effectiveAfterMessageId = resolveEffectiveAfterMessageId({
|
|
265
|
+
requestedAfterMessageId: after_message_id,
|
|
266
|
+
});
|
|
267
|
+
const fetchPlan = planWaitForMessagesFetch({ effectiveAfterMessageId });
|
|
268
|
+
// Without a cursor, page BACKWARDS from the tail (getLatest…) so we
|
|
269
|
+
// return the most-recent messages, not the oldest N; with a cursor we
|
|
270
|
+
// keep the unchanged forward "everything after the cursor" behavior.
|
|
271
|
+
const existing = fetchPlan.mode === "catch_up_tail"
|
|
272
|
+
? await getLatestLocalChatMessages(effectiveLocalRoomId, {
|
|
273
|
+
limit: fetchPlan.limit,
|
|
274
|
+
include_prompt_only: true,
|
|
275
|
+
})
|
|
276
|
+
: await getLocalChatMessages(effectiveLocalRoomId, {
|
|
277
|
+
after: effectiveAfterMessageId,
|
|
278
|
+
include_prompt_only: true,
|
|
279
|
+
});
|
|
280
|
+
const replayingExistingMessages = existing.messages.length > 0;
|
|
281
|
+
const result = replayingExistingMessages
|
|
282
|
+
? existing
|
|
283
|
+
: await waitForLocalChatMessages(effectiveLocalRoomId, {
|
|
284
|
+
after: effectiveAfterMessageId,
|
|
285
|
+
timeoutMs: serverTimeout,
|
|
286
|
+
include_prompt_only: true,
|
|
287
|
+
});
|
|
288
|
+
const messages = await attachLocalActivationMetadata(effectiveLocalRoomId, result.messages, agentSession, {
|
|
289
|
+
includeTaskOwnerLeases: !replayingExistingMessages,
|
|
290
|
+
});
|
|
291
|
+
const routing = filterSilentActivationMessages(messages);
|
|
292
|
+
touchRoomSession(effectiveLocalRoomId, routing.last_observed_message_id ?? getLastMessageId(result));
|
|
293
|
+
const threadContext = await collectThreadContextMessages({
|
|
294
|
+
messages: routing.messages,
|
|
295
|
+
localRoomId: effectiveLocalRoomId,
|
|
296
|
+
roomId: targetRoomId,
|
|
297
|
+
projectId: targetProjectId,
|
|
298
|
+
});
|
|
299
|
+
return jsonToolResponse({
|
|
300
|
+
room_id: effectiveLocalRoomId,
|
|
301
|
+
...addActivationRoutingTelemetry({
|
|
302
|
+
messages: toAgentReadableMessages(routing.messages, threadContext),
|
|
303
|
+
}, routing),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
await syncRoomPresence(targetRoomId ?? currentRoom?.room_id ?? null, identity, getRememberedRoomPresence(targetRoomId ?? currentRoom?.room_id ?? null, agentSession ? identityFromAgentSession(agentSession) : identity), agentSession);
|
|
307
|
+
const clientTimeout = serverTimeout + (serverTimeout > 120_000 ? 120_000 : 5_000);
|
|
308
|
+
const effectiveAfterMessageId = resolveEffectiveAfterMessageId({
|
|
309
|
+
requestedAfterMessageId: after_message_id,
|
|
310
|
+
});
|
|
311
|
+
const fetchPlan = planWaitForMessagesFetch({ effectiveAfterMessageId });
|
|
312
|
+
const deliveryHeaders = buildAgentDeliveryHeaders(agentSession);
|
|
313
|
+
const allMessages = [];
|
|
314
|
+
let roomIdFromResponse;
|
|
315
|
+
// Long-poll the server (blocks up to serverTimeout for new messages),
|
|
316
|
+
// optionally seeded with a cursor. Shared by the cursor path and by the
|
|
317
|
+
// no-cursor empty-tail fallback so a quiet room still blocks instead of
|
|
318
|
+
// busy-spinning. When `after` is provided this also catches up any backlog
|
|
319
|
+
// after the cursor via forward pagination.
|
|
320
|
+
const longPollFromCursor = async (after) => {
|
|
321
|
+
const params = new URLSearchParams();
|
|
322
|
+
if (after)
|
|
323
|
+
params.set("after", after);
|
|
324
|
+
params.set("timeout", String(serverTimeout));
|
|
325
|
+
const queryString = params.toString();
|
|
326
|
+
const firstResult = await roomScopedApiCall({
|
|
327
|
+
room_id: targetRoomId,
|
|
328
|
+
project_id: targetProjectId,
|
|
329
|
+
room_path: (targetRoomId) => appendIncludePromptOnly(`/rooms/${encodeRoomIdPath(targetRoomId)}/messages/poll?${queryString}`),
|
|
330
|
+
project_path: (targetProjectId) => appendIncludePromptOnly(`/projects/${encodeURIComponent(targetProjectId)}/messages/poll?${queryString}`),
|
|
331
|
+
options: buildWaitForMessagesRequestOptions({
|
|
332
|
+
deliveryHeaders,
|
|
333
|
+
signal: AbortSignal.timeout(clientTimeout),
|
|
334
|
+
}),
|
|
335
|
+
});
|
|
336
|
+
allMessages.push(...(firstResult.messages ?? []));
|
|
337
|
+
roomIdFromResponse = roomIdFromResponse || firstResult.room_id || firstResult.project_id;
|
|
338
|
+
if (firstResult.has_more && allMessages.length > 0) {
|
|
339
|
+
let afterCursor = allMessages[allMessages.length - 1]?.id;
|
|
340
|
+
while (afterCursor) {
|
|
341
|
+
const pageParams = new URLSearchParams();
|
|
342
|
+
pageParams.set("after", afterCursor);
|
|
343
|
+
const qs = pageParams.toString();
|
|
344
|
+
const page = await roomScopedApiCall(buildWaitForMessagesHistoryPageRequest({
|
|
345
|
+
targetRoomId,
|
|
346
|
+
targetProjectId,
|
|
347
|
+
queryString: qs,
|
|
348
|
+
deliveryHeaders,
|
|
349
|
+
}));
|
|
350
|
+
const msgs = page.messages ?? [];
|
|
351
|
+
allMessages.push(...msgs);
|
|
352
|
+
if (!page.has_more || msgs.length === 0)
|
|
353
|
+
break;
|
|
354
|
+
afterCursor = msgs[msgs.length - 1]?.id;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
if (fetchPlan.mode === "catch_up_tail") {
|
|
359
|
+
// No cursor: catch up on only the bounded recent tail. Mirror
|
|
360
|
+
// read_messages by paging BACKWARDS from the tail (before=latest) so we
|
|
361
|
+
// return the newest N and never walk the full room history. This also
|
|
362
|
+
// advances the session cursor to the newest message returned.
|
|
363
|
+
const recent = await fetchRecentRemoteMessages({
|
|
364
|
+
targetRoomId,
|
|
365
|
+
targetProjectId,
|
|
366
|
+
limit: fetchPlan.limit,
|
|
367
|
+
});
|
|
368
|
+
if (recent.messages.length > 0) {
|
|
369
|
+
allMessages.push(...recent.messages);
|
|
370
|
+
roomIdFromResponse = recent.roomIdFromResponse;
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
// Empty tail => the room is effectively empty, so there is nothing to
|
|
374
|
+
// dump. Fall through to the long-poll (no cursor) so a worker looping
|
|
375
|
+
// in a quiet room blocks up to the timeout instead of busy-spinning,
|
|
376
|
+
// exactly as the local path does via waitForLocalChatMessages.
|
|
377
|
+
roomIdFromResponse = recent.roomIdFromResponse;
|
|
378
|
+
await longPollFromCursor();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
await longPollFromCursor(fetchPlan.after);
|
|
383
|
+
}
|
|
384
|
+
const routing = filterSilentActivationMessages(allMessages);
|
|
385
|
+
const threadContext = await collectThreadContextMessages({
|
|
386
|
+
messages: routing.messages,
|
|
387
|
+
localRoomId,
|
|
388
|
+
roomId: targetRoomId,
|
|
389
|
+
projectId: targetProjectId,
|
|
390
|
+
});
|
|
391
|
+
const output = addActivationRoutingTelemetry({
|
|
392
|
+
messages: toAgentReadableMessages(routing.messages, threadContext),
|
|
393
|
+
}, routing);
|
|
394
|
+
if (roomIdFromResponse) {
|
|
395
|
+
output[targetRoomId ? "room_id" : "project_id"] = roomIdFromResponse;
|
|
396
|
+
}
|
|
397
|
+
if (targetRoomId) {
|
|
398
|
+
touchRoomSession(targetRoomId, routing.last_observed_message_id ?? getLastMessageId(output));
|
|
399
|
+
}
|
|
400
|
+
await syncRoomPresence(targetRoomId ?? currentRoom?.room_id ?? null, identity, getRememberedRoomPresence(targetRoomId ?? currentRoom?.room_id ?? null, agentSession ? identityFromAgentSession(agentSession) : identity), agentSession);
|
|
401
|
+
return jsonToolResponse(output);
|
|
402
|
+
});
|
|
403
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerMessageTools, registerStatusTools } from "./messages/index.js";
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getRoomFromConfig } from "../../../config-reader.js";
|
|
3
|
+
import { looksLikeInviteCode } from "../../../room-id.js";
|
|
4
|
+
import { apiCall, clearAuthenticatedAccountCache, clearPendingDeviceAuth, clearStoredAuth, currentRoom, ensureAgentIdentity, getPendingDeviceAuth, joinRoomIdentifierWithoutImplicitGitRefCreate, setAuthenticatedAccountCache, setPendingDeviceAuth, setStoredAuth, toPublicAgentIdentity, withCanonicalRoomLink, } from "../../runtime.js";
|
|
5
|
+
import { jsonTextResponse } from "./responses.js";
|
|
6
|
+
import { workerModeDisabledToolResult } from "../../runtime/worker-bearer.js";
|
|
7
|
+
export function registerDeviceAuthTools(server) {
|
|
8
|
+
registerStartDeviceAuthTool(server);
|
|
9
|
+
registerPollDeviceAuthTool(server);
|
|
10
|
+
registerClearSavedAuthTool(server);
|
|
11
|
+
}
|
|
12
|
+
function registerStartDeviceAuthTool(server) {
|
|
13
|
+
server.tool("start_device_auth", "Start GitHub Device Flow for Let Agents Chat and persist the pending request locally. Use this when private repo access or explicit LetAgents auth is needed.", {
|
|
14
|
+
room_id: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Optional room to associate with this auth request for later auto-join."),
|
|
18
|
+
force: z
|
|
19
|
+
.boolean()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("If true, replaces any existing pending device auth request."),
|
|
22
|
+
}, async ({ room_id, force }) => {
|
|
23
|
+
const disabled = workerModeDisabledToolResult();
|
|
24
|
+
if (disabled)
|
|
25
|
+
return jsonTextResponse(disabled);
|
|
26
|
+
const existing = getPendingDeviceAuth();
|
|
27
|
+
if (existing && !force) {
|
|
28
|
+
return jsonTextResponse({
|
|
29
|
+
success: true,
|
|
30
|
+
reused_existing_request: true,
|
|
31
|
+
...existing,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const response = await apiCall("/auth/device/start", {
|
|
35
|
+
method: "POST",
|
|
36
|
+
});
|
|
37
|
+
const pendingAuth = setPendingDeviceAuth({
|
|
38
|
+
request_id: response.request_id,
|
|
39
|
+
user_code: response.user_code,
|
|
40
|
+
verification_uri: response.verification_uri,
|
|
41
|
+
interval_seconds: response.interval,
|
|
42
|
+
expires_at: new Date(Date.now() + response.expires_in * 1000).toISOString(),
|
|
43
|
+
started_at: new Date().toISOString(),
|
|
44
|
+
suggested_room_id: room_id ?? currentRoom?.room_id ?? getRoomFromConfig() ?? undefined,
|
|
45
|
+
});
|
|
46
|
+
return jsonTextResponse({
|
|
47
|
+
success: true,
|
|
48
|
+
...pendingAuth,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function registerPollDeviceAuthTool(server) {
|
|
53
|
+
server.tool("poll_device_auth", "Poll a pending GitHub Device Flow request. On success this stores the LetAgents token locally and can optionally join a room immediately.", {
|
|
54
|
+
request_id: z
|
|
55
|
+
.string()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("The device auth request to poll. Defaults to the locally saved pending request."),
|
|
58
|
+
room_id: z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("Optional room to auto-join after authorization succeeds."),
|
|
62
|
+
auto_join: z
|
|
63
|
+
.boolean()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("If true, tries to join the room immediately after the auth succeeds."),
|
|
66
|
+
}, async ({ request_id, room_id, auto_join }) => {
|
|
67
|
+
const disabled = workerModeDisabledToolResult();
|
|
68
|
+
if (disabled)
|
|
69
|
+
return jsonTextResponse(disabled);
|
|
70
|
+
const pendingAuth = request_id
|
|
71
|
+
? getPendingDeviceAuth()?.request_id === request_id
|
|
72
|
+
? getPendingDeviceAuth()
|
|
73
|
+
: null
|
|
74
|
+
: getPendingDeviceAuth();
|
|
75
|
+
if (!pendingAuth && !request_id) {
|
|
76
|
+
return jsonTextResponse({
|
|
77
|
+
success: false,
|
|
78
|
+
error: "No pending device auth request found.",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const requestId = request_id || pendingAuth?.request_id;
|
|
82
|
+
if (!requestId) {
|
|
83
|
+
return jsonTextResponse({
|
|
84
|
+
success: false,
|
|
85
|
+
error: "A request_id is required when nothing is saved locally.",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const result = await apiCall(`/auth/device/poll/${encodeURIComponent(requestId)}`);
|
|
89
|
+
if (result.status === "pending" || result.status === "slow_down") {
|
|
90
|
+
if (pendingAuth) {
|
|
91
|
+
setPendingDeviceAuth({
|
|
92
|
+
...pendingAuth,
|
|
93
|
+
interval_seconds: result.interval ?? pendingAuth.interval_seconds,
|
|
94
|
+
expires_at: result.expires_in !== undefined
|
|
95
|
+
? new Date(Date.now() + result.expires_in * 1000).toISOString()
|
|
96
|
+
: pendingAuth.expires_at,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return jsonTextResponse({ success: true, ...result });
|
|
100
|
+
}
|
|
101
|
+
if (result.status === "denied" || result.status === "expired") {
|
|
102
|
+
clearPendingDeviceAuth();
|
|
103
|
+
clearStoredAuth();
|
|
104
|
+
clearAuthenticatedAccountCache();
|
|
105
|
+
return jsonTextResponse({ success: false, ...result });
|
|
106
|
+
}
|
|
107
|
+
if (!result.letagents_token) {
|
|
108
|
+
throw new Error("Device auth completed without a LetAgents token.");
|
|
109
|
+
}
|
|
110
|
+
clearPendingDeviceAuth();
|
|
111
|
+
const storedAuth = setStoredAuth({
|
|
112
|
+
token: result.letagents_token,
|
|
113
|
+
expires_at: result.expires_at,
|
|
114
|
+
account: result.account,
|
|
115
|
+
stored_at: new Date().toISOString(),
|
|
116
|
+
source: "device_flow",
|
|
117
|
+
});
|
|
118
|
+
setAuthenticatedAccountCache(storedAuth.account ?? undefined, storedAuth.account ? "stored" : null, null);
|
|
119
|
+
let joinedRoom = null;
|
|
120
|
+
const roomToJoin = room_id ||
|
|
121
|
+
pendingAuth?.suggested_room_id ||
|
|
122
|
+
currentRoom?.room_id ||
|
|
123
|
+
getRoomFromConfig() ||
|
|
124
|
+
undefined;
|
|
125
|
+
if (auto_join && roomToJoin) {
|
|
126
|
+
const joinedVia = looksLikeInviteCode(roomToJoin) ? "join_code" : "join_room";
|
|
127
|
+
const joined = await joinRoomIdentifierWithoutImplicitGitRefCreate(roomToJoin, joinedVia, { fallbackToRepo: true });
|
|
128
|
+
joinedRoom = joined.room;
|
|
129
|
+
}
|
|
130
|
+
const agentIdentity = await ensureAgentIdentity();
|
|
131
|
+
return jsonTextResponse({
|
|
132
|
+
success: true,
|
|
133
|
+
status: "authorized",
|
|
134
|
+
account: storedAuth.account ?? null,
|
|
135
|
+
expires_at: storedAuth.expires_at ?? null,
|
|
136
|
+
auto_joined_room: joinedRoom,
|
|
137
|
+
...(joinedRoom?.room_id
|
|
138
|
+
? withCanonicalRoomLink(joinedRoom.room_id, {})
|
|
139
|
+
: {}),
|
|
140
|
+
agent_identity: toPublicAgentIdentity(agentIdentity),
|
|
141
|
+
hint: "You can change your agent's display name anytime using the set_agent_name tool.",
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function registerClearSavedAuthTool(server) {
|
|
146
|
+
server.tool("clear_saved_auth", "Clear any locally saved LetAgents auth token and pending device auth request.", {}, async () => {
|
|
147
|
+
const disabled = workerModeDisabledToolResult();
|
|
148
|
+
if (disabled)
|
|
149
|
+
return jsonTextResponse(disabled);
|
|
150
|
+
clearPendingDeviceAuth();
|
|
151
|
+
clearStoredAuth();
|
|
152
|
+
clearAuthenticatedAccountCache();
|
|
153
|
+
return jsonTextResponse({
|
|
154
|
+
success: true,
|
|
155
|
+
env_token_still_present: Boolean(process.env.LETAGENTS_TOKEN),
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { buildAgentActorLabel, formatOwnerAttribution } from "../../../../shared/agent-identity.js";
|
|
3
|
+
import { normalizeAgentBaseName } from "../../../../shared/codenames.js";
|
|
4
|
+
import { apiCall, currentAgentIdentity, currentAgentIdentityKey, detectAgentIdeLabel, getConversationIdentity, getLetagentsToken, resolveOwnerContext, setConversationIdentity, storeCurrentAgentIdentity, toPublicAgentIdentity, } from "../../runtime.js";
|
|
5
|
+
import { jsonTextResponse } from "./responses.js";
|
|
6
|
+
import { workerModeDisabledToolResult } from "../../runtime/worker-bearer.js";
|
|
7
|
+
export function registerSetAgentNameTool(server) {
|
|
8
|
+
server.tool("set_agent_name", "Set or change the agent's display name. The agent will be known by this name in the room. Use this to pick a custom name instead of the auto-generated codename.", {
|
|
9
|
+
name: z
|
|
10
|
+
.string()
|
|
11
|
+
.min(2)
|
|
12
|
+
.max(64)
|
|
13
|
+
.describe("The desired display name for this agent (2-64 characters)."),
|
|
14
|
+
conversation_id: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Optional conversation ID to scope this name change. When provided, only this conversation uses the new name; other conversations keep their own identity."),
|
|
18
|
+
}, async ({ name: desiredName, conversation_id }) => {
|
|
19
|
+
const disabled = workerModeDisabledToolResult();
|
|
20
|
+
if (disabled)
|
|
21
|
+
return jsonTextResponse(disabled);
|
|
22
|
+
const trimmedName = desiredName.trim();
|
|
23
|
+
if (trimmedName.length < 2 || trimmedName.length > 64) {
|
|
24
|
+
return jsonTextResponse({
|
|
25
|
+
success: false,
|
|
26
|
+
error: "Name must be between 2 and 64 characters.",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const authAvailable = Boolean(await getLetagentsToken());
|
|
30
|
+
if (!authAvailable) {
|
|
31
|
+
return jsonTextResponse({
|
|
32
|
+
success: false,
|
|
33
|
+
error: "Authentication required. Run start_device_auth first.",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const slugName = normalizeAgentBaseName(trimmedName);
|
|
37
|
+
try {
|
|
38
|
+
const owner = await resolveOwnerContext();
|
|
39
|
+
const ideLabel = detectAgentIdeLabel();
|
|
40
|
+
const ownerAttribution = formatOwnerAttribution(owner.label);
|
|
41
|
+
const actorLabel = buildAgentActorLabel({
|
|
42
|
+
display_name: trimmedName,
|
|
43
|
+
owner_label: owner.label,
|
|
44
|
+
ide_label: ideLabel,
|
|
45
|
+
});
|
|
46
|
+
let canonicalKey = owner.login ? `${owner.login}/${slugName}` : null;
|
|
47
|
+
if (!conversation_id) {
|
|
48
|
+
const registered = await apiCall("/agents", {
|
|
49
|
+
method: "POST",
|
|
50
|
+
body: JSON.stringify({
|
|
51
|
+
name: slugName,
|
|
52
|
+
display_name: trimmedName,
|
|
53
|
+
owner_label: owner.label,
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
if (typeof registered.canonical_key === "string") {
|
|
57
|
+
canonicalKey = registered.canonical_key;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const updatedIdentity = {
|
|
61
|
+
name: slugName,
|
|
62
|
+
display_name: trimmedName,
|
|
63
|
+
owner_label: owner.label,
|
|
64
|
+
owner_attribution: ownerAttribution,
|
|
65
|
+
ide_label: ideLabel,
|
|
66
|
+
actor_label: actorLabel,
|
|
67
|
+
canonical_key: canonicalKey,
|
|
68
|
+
runtime_key: currentAgentIdentityKey,
|
|
69
|
+
source: conversation_id ? "local" : "api",
|
|
70
|
+
resolved_at: new Date().toISOString(),
|
|
71
|
+
};
|
|
72
|
+
if (conversation_id) {
|
|
73
|
+
setConversationIdentity(conversation_id, updatedIdentity);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
storeCurrentAgentIdentity(updatedIdentity, currentAgentIdentityKey);
|
|
77
|
+
}
|
|
78
|
+
return jsonTextResponse({
|
|
79
|
+
success: true,
|
|
80
|
+
message: `Agent name changed to "${trimmedName}".`,
|
|
81
|
+
agent_identity: toPublicAgentIdentity(conversation_id
|
|
82
|
+
? getConversationIdentity(conversation_id)
|
|
83
|
+
: currentAgentIdentity),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
return jsonTextResponse({
|
|
88
|
+
success: false,
|
|
89
|
+
error: `Failed to set name: ${error instanceof Error ? error.message : String(error)}`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|