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,3 +1,13 @@
|
|
|
1
|
+
import { normalizeRoutingHandle, normalizeRoutingSender, routingIdentityAliases, routingSenderAliasRows, routingSenderAliases, } from "../../shared/routing-aliases.mjs";
|
|
2
|
+
import { parsePositivePgIntegerScopedId } from "./scoped-ids.js";
|
|
3
|
+
/**
|
|
4
|
+
* Repository events can contain text written by any external contributor.
|
|
5
|
+
* They remain visible room activity, but their text is never an instruction
|
|
6
|
+
* channel for a managed local worker.
|
|
7
|
+
*/
|
|
8
|
+
export function isUntrustedExternalActivationSource(source) {
|
|
9
|
+
return normalizeSender(source) === "github";
|
|
10
|
+
}
|
|
1
11
|
const NON_AGENT_AT_HANDLES = new Set([
|
|
2
12
|
"charset",
|
|
3
13
|
"container",
|
|
@@ -33,6 +43,54 @@ export function attachAgentMessageActivation(message, identity, context = {}) {
|
|
|
33
43
|
},
|
|
34
44
|
};
|
|
35
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Send-time receipts are the activation authority. For a snapshot-bearing
|
|
48
|
+
* message, a receipt activates and the absence of one is the durable
|
|
49
|
+
* send-time "silent" — never re-promoted by re-running the router against
|
|
50
|
+
* later task/thread/session state, which would create a second authority.
|
|
51
|
+
* Only messages that predate routing snapshots keep the lazy per-reader
|
|
52
|
+
* decision, so legacy backlog mentions still activate rotated sessions.
|
|
53
|
+
*/
|
|
54
|
+
export function attachAgentMessageActivationsFromReceipts(messages, identity, receiptsMap, snapshotNumbers, context = {}) {
|
|
55
|
+
if (!identity || identity.session_kind !== "worker") {
|
|
56
|
+
return [...messages];
|
|
57
|
+
}
|
|
58
|
+
return messages.map((message) => {
|
|
59
|
+
const msgIdStr = String(message.id ?? "");
|
|
60
|
+
const msgNum = parsePositivePgIntegerScopedId(msgIdStr, "msg");
|
|
61
|
+
const receipt = msgNum !== null ? receiptsMap.get(msgNum) || receiptsMap.get(msgIdStr) : null;
|
|
62
|
+
if (receipt) {
|
|
63
|
+
const reason = receipt.activation_reason;
|
|
64
|
+
return {
|
|
65
|
+
...message,
|
|
66
|
+
activation: {
|
|
67
|
+
for_current_agent: {
|
|
68
|
+
decision: "activate",
|
|
69
|
+
reason: reason || "explicit_mention",
|
|
70
|
+
addressed: true,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// System failure rows are canonical silent control events. A routing
|
|
76
|
+
// snapshot with no receipt must not erase their diagnostic reason.
|
|
77
|
+
if (msgNum !== null
|
|
78
|
+
&& snapshotNumbers.has(msgNum)
|
|
79
|
+
&& normalizeSender(message.source) !== "managed_agent_failure") {
|
|
80
|
+
return {
|
|
81
|
+
...message,
|
|
82
|
+
activation: {
|
|
83
|
+
for_current_agent: {
|
|
84
|
+
decision: "silent",
|
|
85
|
+
reason: "unaddressed",
|
|
86
|
+
addressed: false,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return attachAgentMessageActivation(message, identity, context);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
36
94
|
export function attachAgentMessageActivations(messages, identity, context = {}) {
|
|
37
95
|
if (!identity || identity.session_kind !== "worker") {
|
|
38
96
|
return [...messages];
|
|
@@ -40,17 +98,27 @@ export function attachAgentMessageActivations(messages, identity, context = {})
|
|
|
40
98
|
return messages.map((message) => attachAgentMessageActivation(message, identity, context));
|
|
41
99
|
}
|
|
42
100
|
export function decideAgentMessageActivation(message, identity, context = {}) {
|
|
43
|
-
if (normalizeSender(message.source) === "managed_agent_failure"
|
|
101
|
+
if (normalizeSender(message.source) === "managed_agent_failure"
|
|
102
|
+
|| isUntrustedExternalActivationSource(message.source)) {
|
|
44
103
|
return decision("silent", "system_event");
|
|
45
104
|
}
|
|
46
|
-
|
|
105
|
+
const messageId = normalizedString(message.id);
|
|
106
|
+
const authoritativeLegacyDecision = context.authoritativeLegacyDecisions?.get(messageId);
|
|
107
|
+
if (authoritativeLegacyDecision)
|
|
108
|
+
return authoritativeLegacyDecision;
|
|
109
|
+
if (context.selfMessageIds !== undefined
|
|
110
|
+
? context.selfMessageIds.has(messageId)
|
|
111
|
+
: senderMatchesIdentity(message.sender, identity)) {
|
|
47
112
|
return decision("silent", "self_message");
|
|
48
113
|
}
|
|
49
114
|
const mentions = extractMentionHandles(message.text);
|
|
50
115
|
if (mentions.some(isBroadcastHandle)) {
|
|
51
116
|
return decision("activate", "broadcast");
|
|
52
117
|
}
|
|
53
|
-
|
|
118
|
+
const authoritativeExplicitMentions = context.explicitMentionMessageIds;
|
|
119
|
+
if (authoritativeExplicitMentions !== undefined
|
|
120
|
+
? authoritativeExplicitMentions.has(messageId)
|
|
121
|
+
: mentions.some((mention) => activationIdentityAliases(identity).has(normalizeMentionIdentityHandle(mention)))) {
|
|
54
122
|
return decision("activate", "explicit_mention");
|
|
55
123
|
}
|
|
56
124
|
if (hasBroadcastAddress(message.text)) {
|
|
@@ -59,13 +127,22 @@ export function decideAgentMessageActivation(message, identity, context = {}) {
|
|
|
59
127
|
if (mentions.some(isLikelyAgentMentionHandle)) {
|
|
60
128
|
return decision("silent", "explicit_other_mention");
|
|
61
129
|
}
|
|
62
|
-
|
|
130
|
+
const authoritativeThreadParticipantRootIds = context.threadParticipantRootIds;
|
|
131
|
+
const authoritativeReplyTargets = context.replyTargetMessageIds;
|
|
132
|
+
const hasAuthoritativeThreadMembership = isThreadReply(message)
|
|
133
|
+
&& authoritativeThreadParticipantRootIds !== undefined;
|
|
134
|
+
if (authoritativeReplyTargets !== undefined
|
|
135
|
+
? authoritativeReplyTargets.has(messageId)
|
|
136
|
+
: !hasAuthoritativeThreadMembership && senderMatchesIdentity(message.reply_to?.sender, identity)) {
|
|
63
137
|
return decision("activate", "reply_target");
|
|
64
138
|
}
|
|
65
139
|
if (isAgentReplyTarget(message.reply_to) && !isThreadReply(message)) {
|
|
66
140
|
return decision("silent", "other_reply_target");
|
|
67
141
|
}
|
|
68
|
-
if (isThreadReply(message)
|
|
142
|
+
if (isThreadReply(message)
|
|
143
|
+
&& (hasAuthoritativeThreadMembership
|
|
144
|
+
? authoritativeThreadParticipantRootIds.has(threadRootId(message))
|
|
145
|
+
: threadParticipantsIncludeIdentity(message, identity))) {
|
|
69
146
|
return decision("activate", "thread_participant");
|
|
70
147
|
}
|
|
71
148
|
const taskOwnerDecision = decideTaskOwnerActivation(message, identity, context);
|
|
@@ -83,9 +160,12 @@ function decision(decisionValue, reason) {
|
|
|
83
160
|
}
|
|
84
161
|
function isThreadReply(message) {
|
|
85
162
|
const ownId = normalizedString(message.id);
|
|
86
|
-
const rootId =
|
|
163
|
+
const rootId = threadRootId(message);
|
|
87
164
|
return Boolean(ownId && rootId && ownId !== rootId);
|
|
88
165
|
}
|
|
166
|
+
function threadRootId(message) {
|
|
167
|
+
return normalizedString(message.thread_root_id) || normalizedString(message.thread?.root_message_id);
|
|
168
|
+
}
|
|
89
169
|
function isAgentReplyTarget(replyTo) {
|
|
90
170
|
return normalizeSender(replyTo?.source) === "agent";
|
|
91
171
|
}
|
|
@@ -204,24 +284,111 @@ function senderMatchesIdentity(sender, identity) {
|
|
|
204
284
|
const normalizedSender = normalizeSender(sender);
|
|
205
285
|
if (!normalizedSender)
|
|
206
286
|
return false;
|
|
207
|
-
const aliases =
|
|
287
|
+
const aliases = activationIdentityAliases(identity);
|
|
208
288
|
if (aliases.has(normalizedSender))
|
|
209
289
|
return true;
|
|
210
290
|
return String(sender || "")
|
|
211
291
|
.split("|")
|
|
212
292
|
.some((part) => aliases.has(normalizeSender(part)));
|
|
213
293
|
}
|
|
214
|
-
function
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
294
|
+
export function activationIdentityAliases(identity) {
|
|
295
|
+
return routingIdentityAliases(identity);
|
|
296
|
+
}
|
|
297
|
+
/** Canonical aliases materialized from a historical message sender. */
|
|
298
|
+
export function activationSenderAliases(sender, segmentLimit = 16) {
|
|
299
|
+
return routingSenderAliases(sender, segmentLimit);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Resolve identity-bearing addresses against the complete active room
|
|
303
|
+
* population. A display alias is authority only when it names one durable
|
|
304
|
+
* agent key globally; account/provider filtering happens after this step.
|
|
305
|
+
* Full historical sender labels take precedence over their pipe-delimited
|
|
306
|
+
* compatibility segments.
|
|
307
|
+
*/
|
|
308
|
+
export function resolveGloballyAddressedAgentKeys(message, identities) {
|
|
309
|
+
return createGlobalAgentAddressResolver(identities)(message);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Build the room-wide alias authority once, then resolve a page of legacy
|
|
313
|
+
* messages without rebuilding every active worker alias set per message.
|
|
314
|
+
*/
|
|
315
|
+
export function createGlobalAgentAddressResolver(identities) {
|
|
316
|
+
const keysByAlias = new Map();
|
|
317
|
+
for (const identity of identities) {
|
|
318
|
+
const key = normalizedString(identity.agent_key);
|
|
319
|
+
if (!key)
|
|
320
|
+
continue;
|
|
321
|
+
for (const alias of activationIdentityAliases(identity)) {
|
|
322
|
+
const keys = keysByAlias.get(alias) ?? new Set();
|
|
323
|
+
keys.add(key);
|
|
324
|
+
keysByAlias.set(alias, keys);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return (message) => {
|
|
328
|
+
const mentions = extractMentionHandles(message.text);
|
|
329
|
+
const broadcast = mentions.some(isBroadcastHandle) || hasBroadcastAddress(message.text);
|
|
330
|
+
const hasMention = mentions.some((mention) => !isBroadcastHandle(mention));
|
|
331
|
+
const hasAgentMention = mentions.some(isLikelyAgentMentionHandle);
|
|
332
|
+
const explicitMentionKeys = new Set();
|
|
333
|
+
for (const mention of mentions) {
|
|
334
|
+
if (isBroadcastHandle(mention))
|
|
335
|
+
continue;
|
|
336
|
+
const alias = normalizeMentionIdentityHandle(mention);
|
|
337
|
+
if (!alias)
|
|
338
|
+
continue;
|
|
339
|
+
const keys = keysByAlias.get(alias);
|
|
340
|
+
if (keys?.size === 1)
|
|
341
|
+
explicitMentionKeys.add(keys.values().next().value);
|
|
342
|
+
}
|
|
343
|
+
const replyTargetKeys = new Set();
|
|
344
|
+
const replyAliases = normalizedString(message.reply_to?.source) === "agent"
|
|
345
|
+
? routingSenderAliasRows(message.reply_to?.sender)
|
|
346
|
+
: [];
|
|
347
|
+
const matchingKeys = (full) => {
|
|
348
|
+
const keys = new Set();
|
|
349
|
+
for (const row of replyAliases) {
|
|
350
|
+
if (row.isFull !== full)
|
|
351
|
+
continue;
|
|
352
|
+
for (const key of keysByAlias.get(row.alias) ?? [])
|
|
353
|
+
keys.add(key);
|
|
354
|
+
}
|
|
355
|
+
return keys;
|
|
356
|
+
};
|
|
357
|
+
const fullMatches = matchingKeys(true);
|
|
358
|
+
const replyMatches = fullMatches.size > 0 ? fullMatches : matchingKeys(false);
|
|
359
|
+
if (replyMatches.size === 1)
|
|
360
|
+
replyTargetKeys.add(replyMatches.values().next().value);
|
|
361
|
+
const senderKeys = new Set();
|
|
362
|
+
const senderAliases = routingSenderAliasRows(message.sender);
|
|
363
|
+
const senderMatchingKeys = (full) => {
|
|
364
|
+
const keys = new Set();
|
|
365
|
+
for (const row of senderAliases) {
|
|
366
|
+
if (row.isFull !== full)
|
|
367
|
+
continue;
|
|
368
|
+
for (const key of keysByAlias.get(row.alias) ?? [])
|
|
369
|
+
keys.add(key);
|
|
370
|
+
}
|
|
371
|
+
return keys;
|
|
372
|
+
};
|
|
373
|
+
const senderFullMatches = senderMatchingKeys(true);
|
|
374
|
+
const senderMatches = senderFullMatches.size > 0
|
|
375
|
+
? senderFullMatches
|
|
376
|
+
: senderMatchingKeys(false);
|
|
377
|
+
if (senderMatches.size === 1)
|
|
378
|
+
senderKeys.add(senderMatches.values().next().value);
|
|
379
|
+
return {
|
|
380
|
+
broadcast,
|
|
381
|
+
hasMention,
|
|
382
|
+
hasAgentMention,
|
|
383
|
+
explicitMentionKeys,
|
|
384
|
+
replyTargetKeys,
|
|
385
|
+
senderKeys,
|
|
386
|
+
};
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
/** Shared legacy task-follow-up classifier used by API and desktop overlays. */
|
|
390
|
+
export function isTaskOwnerFollowUpMessageText(text) {
|
|
391
|
+
return isTaskOwnerFollowUp(text);
|
|
225
392
|
}
|
|
226
393
|
function extractMentionHandles(text) {
|
|
227
394
|
const raw = typeof text === "string" ? text : "";
|
|
@@ -261,10 +428,10 @@ function normalizedString(value) {
|
|
|
261
428
|
return typeof value === "string" ? value.trim() : "";
|
|
262
429
|
}
|
|
263
430
|
function normalizeSender(value) {
|
|
264
|
-
return
|
|
431
|
+
return normalizeRoutingSender(value);
|
|
265
432
|
}
|
|
266
433
|
function normalizeHandle(value) {
|
|
267
|
-
return
|
|
434
|
+
return normalizeRoutingHandle(value);
|
|
268
435
|
}
|
|
269
436
|
function normalizeMentionIdentityHandle(value) {
|
|
270
437
|
const normalized = normalizeHandle(value);
|
|
@@ -21,6 +21,12 @@ export const ROOM_AGENT_SESSION_KINDS = [
|
|
|
21
21
|
"controller",
|
|
22
22
|
"worker",
|
|
23
23
|
];
|
|
24
|
+
export function isRoomAgentDeliveryCredentialExpired(fence, now = Date.now()) {
|
|
25
|
+
if (fence?.kind !== "bearer" || !fence.expires_at)
|
|
26
|
+
return false;
|
|
27
|
+
const expiresAt = Date.parse(fence.expires_at);
|
|
28
|
+
return Number.isFinite(expiresAt) && expiresAt <= now;
|
|
29
|
+
}
|
|
24
30
|
export function normalizeRoomAgentSessionKind(value) {
|
|
25
31
|
return String(value || "").trim().toLowerCase() === "worker" ? "worker" : "controller";
|
|
26
32
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export const MAC_DESKTOP_PUBLIC_BASE_URL = "https://downloads.letagents.chat";
|
|
2
|
+
function record(value, label) {
|
|
3
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4
|
+
throw new Error(`${label} must be an object.`);
|
|
5
|
+
}
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
function numericVersion(value) {
|
|
9
|
+
if (!/^\d+\.\d+\.\d+$/.test(value)) {
|
|
10
|
+
throw new Error("Desktop release manifest version must be numeric x.y.z.");
|
|
11
|
+
}
|
|
12
|
+
return value.split(".").map(Number);
|
|
13
|
+
}
|
|
14
|
+
function compareVersions(left, right) {
|
|
15
|
+
const leftParts = numericVersion(left);
|
|
16
|
+
const rightParts = numericVersion(right);
|
|
17
|
+
for (let index = 0; index < leftParts.length; index += 1) {
|
|
18
|
+
if (leftParts[index] !== rightParts[index])
|
|
19
|
+
return leftParts[index] - rightParts[index];
|
|
20
|
+
}
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
export function parseMacDesktopPublicReleaseManifest(value, minimumVersion) {
|
|
24
|
+
const manifest = record(value, "Desktop release manifest");
|
|
25
|
+
const version = manifest.version;
|
|
26
|
+
if (manifest.schemaVersion !== 1 || manifest.channel !== "beta") {
|
|
27
|
+
throw new Error("Desktop release manifest has an unsupported contract.");
|
|
28
|
+
}
|
|
29
|
+
if (typeof version !== "string") {
|
|
30
|
+
throw new Error("Desktop release manifest version must be numeric x.y.z.");
|
|
31
|
+
}
|
|
32
|
+
numericVersion(version);
|
|
33
|
+
if (compareVersions(version, minimumVersion) < 0) {
|
|
34
|
+
throw new Error(`Desktop release manifest ${version} is older than ${minimumVersion}.`);
|
|
35
|
+
}
|
|
36
|
+
const rawAssets = record(manifest.assets, "Desktop release assets");
|
|
37
|
+
const checksumsUrl = `${MAC_DESKTOP_PUBLIC_BASE_URL}/desktop/v${version}/checksums.txt`;
|
|
38
|
+
if (manifest.checksumsUrl !== checksumsUrl) {
|
|
39
|
+
throw new Error("Desktop release manifest does not use the immutable public checksum URL.");
|
|
40
|
+
}
|
|
41
|
+
const assets = {};
|
|
42
|
+
for (const architecture of ["arm64", "x64"]) {
|
|
43
|
+
const rawAsset = record(rawAssets[architecture], `${architecture} desktop release asset`);
|
|
44
|
+
const fileName = `LetAgents-${version}-darwin-${architecture}.dmg`;
|
|
45
|
+
const publicUrl = `${MAC_DESKTOP_PUBLIC_BASE_URL}/desktop/v${version}/${fileName}`;
|
|
46
|
+
if (rawAsset.fileName !== fileName || rawAsset.publicUrl !== publicUrl) {
|
|
47
|
+
throw new Error(`${architecture} desktop release asset does not use the immutable public URL.`);
|
|
48
|
+
}
|
|
49
|
+
if (!Number.isSafeInteger(rawAsset.bytes) || Number(rawAsset.bytes) <= 0) {
|
|
50
|
+
throw new Error(`${architecture} desktop release asset must have a positive byte size.`);
|
|
51
|
+
}
|
|
52
|
+
if (typeof rawAsset.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(rawAsset.sha256)) {
|
|
53
|
+
throw new Error(`${architecture} desktop release asset must have a lowercase SHA-256 digest.`);
|
|
54
|
+
}
|
|
55
|
+
assets[architecture] = {
|
|
56
|
+
fileName,
|
|
57
|
+
publicUrl,
|
|
58
|
+
bytes: Number(rawAsset.bytes),
|
|
59
|
+
sha256: rawAsset.sha256,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return { schemaVersion: 1, channel: "beta", version, checksumsUrl, assets };
|
|
63
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const MAC_DESKTOP_BETA_VERSION = "0.1.5";
|
|
2
|
+
const MAC_DESKTOP_PUBLIC_BASE_URL = "https://downloads.letagents.chat/desktop";
|
|
3
|
+
function macDesktopAsset(architecture, sha256) {
|
|
4
|
+
const fileName = `LetAgents-${MAC_DESKTOP_BETA_VERSION}-darwin-${architecture}.dmg`;
|
|
5
|
+
return {
|
|
6
|
+
fileName,
|
|
7
|
+
publicUrl: `${MAC_DESKTOP_PUBLIC_BASE_URL}/v${MAC_DESKTOP_BETA_VERSION}/${fileName}`,
|
|
8
|
+
sha256,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export const MAC_DESKTOP_BETA_RELEASE = {
|
|
12
|
+
version: MAC_DESKTOP_BETA_VERSION,
|
|
13
|
+
tag: `desktop-v${MAC_DESKTOP_BETA_VERSION}`,
|
|
14
|
+
assets: {
|
|
15
|
+
arm64: macDesktopAsset("arm64", "704355796cee5214a9cfbb79105a035e0e00dc3a3aa57be7d389b97cf35fc804"),
|
|
16
|
+
x64: macDesktopAsset("x64", "1e4b0b8b2c42ad8eaaf652fc887ab089445c8dd83233d763a6acdaa6617c2471"),
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
export const MAC_DESKTOP_BETA_CHECKSUM_RELEASES = [
|
|
20
|
+
MAC_DESKTOP_BETA_RELEASE,
|
|
21
|
+
{
|
|
22
|
+
version: "0.1.4",
|
|
23
|
+
assets: {
|
|
24
|
+
arm64: {
|
|
25
|
+
fileName: "LetAgents-0.1.4-darwin-arm64.dmg",
|
|
26
|
+
sha256: "27abe236232d33db10ed4533f4a7443a66f93568e9fa73a2ca472b6467fcf1cb",
|
|
27
|
+
},
|
|
28
|
+
x64: {
|
|
29
|
+
fileName: "LetAgents-0.1.4-darwin-x64.dmg",
|
|
30
|
+
sha256: "4c807ad0c799b4e46ab81d1098e5b65934d6b0b49f0a99ced2713897e1c2bc35",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
version: "0.1.3",
|
|
36
|
+
assets: {
|
|
37
|
+
arm64: {
|
|
38
|
+
fileName: "LetAgents-0.1.3-darwin-arm64.dmg",
|
|
39
|
+
sha256: "6010454bc7375a38571d707f90c077207a7d2b49b01a1db1655b03f4def9b502",
|
|
40
|
+
},
|
|
41
|
+
x64: {
|
|
42
|
+
fileName: "LetAgents-0.1.3-darwin-x64.dmg",
|
|
43
|
+
sha256: "b7574e17ef87aebf418926478de10d9937fd1a96ca0c7a53b826a109302c5560",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
version: "0.1.2",
|
|
49
|
+
assets: {
|
|
50
|
+
arm64: {
|
|
51
|
+
fileName: "LetAgents-0.1.2-darwin-arm64.dmg",
|
|
52
|
+
sha256: "e5355deced8383bc7d024ec60b109a38dde69dfeb6b6339352e1f5bc5c53bd43",
|
|
53
|
+
},
|
|
54
|
+
x64: {
|
|
55
|
+
fileName: "LetAgents-0.1.2-darwin-x64.dmg",
|
|
56
|
+
sha256: "67d2896b806695dae8c0224b3bc2780aee6902e897569c983ecc1bdb0330b6b0",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { POSTGRES_INTEGER_MAX, parsePositivePgIntegerScopedId as parseSharedPositivePgIntegerScopedId, } from "../../shared/message-contracts.mjs";
|
|
2
|
+
export { POSTGRES_INTEGER_MAX };
|
|
3
|
+
/** Parse the canonical `<prefix>_<positive PostgreSQL integer>` wire form. */
|
|
4
|
+
export function parsePositivePgIntegerScopedId(value, prefix) {
|
|
5
|
+
return parseSharedPositivePgIntegerScopedId(value, prefix);
|
|
6
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "letagents",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.13",
|
|
4
4
|
"description": "Let Agents Chat — MCP server for AI agent communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/mcp/server.js",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"dist/mcp/**",
|
|
12
12
|
"dist/api/board-intent-payloads.js",
|
|
13
13
|
"dist/shared/**",
|
|
14
|
+
"shared/**",
|
|
14
15
|
"README.md"
|
|
15
16
|
],
|
|
16
17
|
"scripts": {
|
|
@@ -24,6 +25,9 @@
|
|
|
24
25
|
"test:orchestrator": "node --import tsx --test src/orchestrator/__tests__/*.test.ts",
|
|
25
26
|
"test:web": "node --import tsx --test src/web/tests/*.test.ts src/web/src/composables/*.test.ts",
|
|
26
27
|
"test:dependency-age": "node --test scripts/verify-dependency-age.test.mjs",
|
|
28
|
+
"test:docker-context": "node --test scripts/verify-docker-context.test.mjs",
|
|
29
|
+
"test:workflow-supply-chain": "node --test scripts/verify-workflow-supply-chain.test.mjs",
|
|
30
|
+
"test:notifications": "node --import tsx --test src/api/notifications/__tests__/*.test.ts",
|
|
27
31
|
"verify:dependency-age": "node scripts/verify-dependency-age.mjs",
|
|
28
32
|
"db:generate": "drizzle-kit generate",
|
|
29
33
|
"db:migrate": "tsx src/api/migrate.ts",
|
|
@@ -31,7 +35,7 @@
|
|
|
31
35
|
"prepublishOnly": "npm run build",
|
|
32
36
|
"dev:api": "tsx src/api/server.ts",
|
|
33
37
|
"dev:mcp": "tsx src/mcp/server.ts",
|
|
34
|
-
"dev:desktop": "npm --prefix apps/desktop run dev"
|
|
38
|
+
"dev:desktop": "node scripts/ensure-desktop-dependencies.mjs && npm --prefix apps/desktop run dev"
|
|
35
39
|
},
|
|
36
40
|
"keywords": [
|
|
37
41
|
"mcp",
|
|
@@ -62,8 +66,12 @@
|
|
|
62
66
|
"ws": "8.21.0"
|
|
63
67
|
},
|
|
64
68
|
"overrides": {
|
|
69
|
+
"@hono/node-server": "2.0.10",
|
|
65
70
|
"@esbuild-kit/core-utils": {
|
|
66
71
|
"esbuild": "^0.25.12"
|
|
67
|
-
}
|
|
72
|
+
},
|
|
73
|
+
"fast-uri": "3.1.5",
|
|
74
|
+
"hono": "4.12.34",
|
|
75
|
+
"ip-address": "10.3.1"
|
|
68
76
|
}
|
|
69
77
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const POSTGRES_INTEGER_MAX: number;
|
|
2
|
+
export const MESSAGE_SENDER_MAX_CODE_POINTS: number;
|
|
3
|
+
export const MESSAGE_SENDER_MAX_UTF8_BYTES: number;
|
|
4
|
+
export function parsePositivePgIntegerScopedId(value: unknown, prefix: string): number | null;
|
|
5
|
+
export function isMessageSenderWithinBounds(sender: unknown): sender is string;
|
|
6
|
+
export type ParsedAccountAgentRouting =
|
|
7
|
+
| { version: 1; authority: "invalid" }
|
|
8
|
+
| {
|
|
9
|
+
version: 1;
|
|
10
|
+
authority: "receipts";
|
|
11
|
+
recipientAgentKeys: string[];
|
|
12
|
+
recipientSessions: Array<{
|
|
13
|
+
agentKey: string;
|
|
14
|
+
agentSessionId: string;
|
|
15
|
+
successorAgentSessionId?: string;
|
|
16
|
+
}>;
|
|
17
|
+
controlAuthorized: boolean;
|
|
18
|
+
}
|
|
19
|
+
| {
|
|
20
|
+
version: 1;
|
|
21
|
+
authority: "legacy";
|
|
22
|
+
recipientAgentKeys: string[];
|
|
23
|
+
recipientSessions: Array<{
|
|
24
|
+
agentKey: string;
|
|
25
|
+
agentSessionId: string;
|
|
26
|
+
activationReason: string;
|
|
27
|
+
}>;
|
|
28
|
+
controlAuthorized: boolean;
|
|
29
|
+
};
|
|
30
|
+
export function parseAccountAgentRoutingEnvelope(
|
|
31
|
+
routing: unknown,
|
|
32
|
+
): ParsedAccountAgentRouting | undefined;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { normalizeRoutingSender } from "./routing-aliases.mjs";
|
|
2
|
+
|
|
3
|
+
export const POSTGRES_INTEGER_MAX = 2_147_483_647;
|
|
4
|
+
export const MESSAGE_SENDER_MAX_CODE_POINTS = 512;
|
|
5
|
+
export const MESSAGE_SENDER_MAX_UTF8_BYTES = 2_048;
|
|
6
|
+
|
|
7
|
+
/** Parse the canonical `<prefix>_<positive PostgreSQL integer>` wire form. */
|
|
8
|
+
export function parsePositivePgIntegerScopedId(value, prefix) {
|
|
9
|
+
if (typeof value !== "string" || typeof prefix !== "string" || !prefix) return null;
|
|
10
|
+
const marker = `${prefix}_`;
|
|
11
|
+
if (!value.startsWith(marker)) return null;
|
|
12
|
+
const decimal = value.slice(marker.length);
|
|
13
|
+
if (!/^[1-9]\d*$/.test(decimal)) return null;
|
|
14
|
+
const parsed = Number(decimal);
|
|
15
|
+
return Number.isSafeInteger(parsed) && parsed <= POSTGRES_INTEGER_MAX ? parsed : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Bound sender labels before routing projections duplicate their raw value. */
|
|
19
|
+
export function isMessageSenderWithinBounds(sender) {
|
|
20
|
+
if (typeof sender !== "string") return false;
|
|
21
|
+
let codePoints = 0;
|
|
22
|
+
for (const _character of sender) {
|
|
23
|
+
codePoints += 1;
|
|
24
|
+
if (codePoints > MESSAGE_SENDER_MAX_CODE_POINTS) return false;
|
|
25
|
+
}
|
|
26
|
+
return new TextEncoder().encode(sender).byteLength <= MESSAGE_SENDER_MAX_UTF8_BYTES;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Parse the account-scoped routing envelope shared by Desktop and MCP.
|
|
31
|
+
* Undefined means an older/local message supplied no authority. Any present
|
|
32
|
+
* malformed shape returns explicit invalid authority and must never fall back
|
|
33
|
+
* to mutable display aliases.
|
|
34
|
+
*/
|
|
35
|
+
export function parseAccountAgentRoutingEnvelope(routing) {
|
|
36
|
+
if (routing === undefined) return undefined;
|
|
37
|
+
if (!routing || typeof routing !== "object" || routing.version !== 1) {
|
|
38
|
+
return { version: 1, authority: "invalid" };
|
|
39
|
+
}
|
|
40
|
+
if (routing.authority !== "receipts" && routing.authority !== "legacy") {
|
|
41
|
+
return { version: 1, authority: "invalid" };
|
|
42
|
+
}
|
|
43
|
+
if (
|
|
44
|
+
!Array.isArray(routing.recipient_agent_keys)
|
|
45
|
+
|| !routing.recipient_agent_keys.every((value) => typeof value === "string")
|
|
46
|
+
|| !Array.isArray(routing.recipient_agent_sessions)
|
|
47
|
+
) {
|
|
48
|
+
return { version: 1, authority: "invalid" };
|
|
49
|
+
}
|
|
50
|
+
const recipientAgentKeys = routing.recipient_agent_keys.map(normalizeRoutingSender);
|
|
51
|
+
if (recipientAgentKeys.some((value) => !value)) {
|
|
52
|
+
return { version: 1, authority: "invalid" };
|
|
53
|
+
}
|
|
54
|
+
const uniqueKeys = new Set(recipientAgentKeys);
|
|
55
|
+
if (uniqueKeys.size !== recipientAgentKeys.length) {
|
|
56
|
+
return { version: 1, authority: "invalid" };
|
|
57
|
+
}
|
|
58
|
+
const targetKeys = new Set();
|
|
59
|
+
const recipientSessions = [];
|
|
60
|
+
for (const value of routing.recipient_agent_sessions) {
|
|
61
|
+
if (!value || typeof value !== "object") {
|
|
62
|
+
return { version: 1, authority: "invalid" };
|
|
63
|
+
}
|
|
64
|
+
const agentKey = typeof value.agent_key === "string"
|
|
65
|
+
? normalizeRoutingSender(value.agent_key)
|
|
66
|
+
: "";
|
|
67
|
+
const agentSessionId = typeof value.agent_session_id === "string"
|
|
68
|
+
? value.agent_session_id.trim()
|
|
69
|
+
: "";
|
|
70
|
+
if (!agentKey || !agentSessionId || targetKeys.has(agentKey)) {
|
|
71
|
+
return { version: 1, authority: "invalid" };
|
|
72
|
+
}
|
|
73
|
+
targetKeys.add(agentKey);
|
|
74
|
+
if (routing.authority === "receipts") {
|
|
75
|
+
const successorAgentSessionId = value.successor_agent_session_id === undefined
|
|
76
|
+
? undefined
|
|
77
|
+
: typeof value.successor_agent_session_id === "string"
|
|
78
|
+
? value.successor_agent_session_id.trim()
|
|
79
|
+
: "";
|
|
80
|
+
if (successorAgentSessionId !== undefined && !successorAgentSessionId) {
|
|
81
|
+
return { version: 1, authority: "invalid" };
|
|
82
|
+
}
|
|
83
|
+
recipientSessions.push({
|
|
84
|
+
agentKey,
|
|
85
|
+
agentSessionId,
|
|
86
|
+
...(successorAgentSessionId ? { successorAgentSessionId } : {}),
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
const activationReason = typeof value.activation_reason === "string"
|
|
90
|
+
? value.activation_reason.trim()
|
|
91
|
+
: "";
|
|
92
|
+
if (!activationReason) return { version: 1, authority: "invalid" };
|
|
93
|
+
recipientSessions.push({ agentKey, agentSessionId, activationReason });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (
|
|
97
|
+
targetKeys.size !== uniqueKeys.size
|
|
98
|
+
|| [...uniqueKeys].some((key) => !targetKeys.has(key))
|
|
99
|
+
) {
|
|
100
|
+
return { version: 1, authority: "invalid" };
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
version: 1,
|
|
104
|
+
authority: routing.authority,
|
|
105
|
+
recipientAgentKeys: [...uniqueKeys],
|
|
106
|
+
recipientSessions,
|
|
107
|
+
controlAuthorized: routing.control_authorized === true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type RoutingIdentityLike = {
|
|
2
|
+
actorLabel?: string | null;
|
|
3
|
+
actor_label?: string | null;
|
|
4
|
+
displayName?: string | null;
|
|
5
|
+
display_name?: string | null;
|
|
6
|
+
agentKey?: string | null;
|
|
7
|
+
agent_key?: string | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function normalizeRoutingSender(value: unknown): string;
|
|
11
|
+
export function normalizeRoutingHandle(value: unknown): string;
|
|
12
|
+
export function routingIdentityAliases(identity: RoutingIdentityLike): Set<string>;
|
|
13
|
+
export function routingSenderAliasRows(
|
|
14
|
+
sender: unknown,
|
|
15
|
+
segmentLimit?: number,
|
|
16
|
+
): Array<{ alias: string; isFull: boolean }>;
|
|
17
|
+
export function routingSenderAliases(sender: unknown, segmentLimit?: number): Set<string>;
|
|
18
|
+
export function routingAliasHash(alias: string): string;
|