letagents 0.12.20 → 0.12.21

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.
@@ -1,3 +1,4 @@
1
+ import { captureLocalSupervisedRouting, ensureLocalSupervisedRoutingSchema, runLocalSupervisedMessageWrite } from "../../../shared/local-supervised-routing.mjs";
1
2
  import { createRequire } from "node:module";
2
3
  import { mkdir, readFile } from "node:fs/promises";
3
4
  import { homedir } from "node:os";
@@ -176,6 +177,7 @@ async function initializeDb() {
176
177
  database.exec("PRAGMA foreign_keys = ON");
177
178
  database.exec("PRAGMA busy_timeout = 5000");
178
179
  await runLocalSqliteWriteTransactionAsync(database, () => {
180
+ ensureLocalSupervisedRoutingSchema(database);
179
181
  database.exec(`
180
182
  CREATE TABLE IF NOT EXISTS local_chat_room_sequences (
181
183
  room_id TEXT PRIMARY KEY,
@@ -527,7 +529,7 @@ export async function addLocalChatMessage(roomId, input) {
527
529
  }
528
530
  }
529
531
  const timestamp = new Date().toISOString();
530
- const row = await runLocalSqliteWriteTransactionAsync(database, () => withWorkerStateFence(() => {
532
+ const row = await runLocalSupervisedMessageWrite(database, trimmedRoomId, threadRootNumber, () => withWorkerStateFence(() => {
531
533
  const number = allocateLocalMessageNumber(database, trimmedRoomId);
532
534
  const insertedRow = {
533
535
  room_id: trimmedRoomId,
@@ -559,6 +561,7 @@ export async function addLocalChatMessage(roomId, input) {
559
561
  `)
560
562
  .run(insertedRow.room_id, insertedRow.number, insertedRow.reply_to_number, insertedRow.thread_root_number, insertedRow.sender, insertedRow.text, insertedRow.agent_prompt_kind, insertedRow.source, insertedRow.publisher_agent_key, insertedRow.publisher_agent_session_id, insertedRow.timestamp);
561
563
  projectLocalThreadRoutingMessage(database, insertedRow);
564
+ captureLocalSupervisedRouting(database, insertedRow);
562
565
  return insertedRow;
563
566
  }));
564
567
  return {
@@ -1,3 +1,4 @@
1
+ import { isLocalRoomApi, roomApiOrigin } from "../../../../shared/room-api-origin.mjs";
1
2
  import { createHash, randomUUID } from "node:crypto";
2
3
  import { lstat, readFile, realpath } from "node:fs/promises";
3
4
  import { createConnection } from "node:net";
@@ -356,6 +357,9 @@ export async function borrowSupervisedWorkerCredential(session, env = process.en
356
357
  }
357
358
  function normalizedWorkerApiOrigin(env) {
358
359
  const apiUrl = env.LETAGENTS_API_URL?.trim() || "https://letagents.chat";
360
+ if (isLocalRoomApi(apiUrl) && env.LETAGENTS_SUPERVISED_BOUNDED_TURNS === "1"
361
+ && env.LETAGENTS_EXECUTION_PROFILE === "supervised_room_turn")
362
+ return apiUrl;
359
363
  let parsed;
360
364
  try {
361
365
  parsed = new URL(apiUrl);
@@ -452,7 +456,7 @@ function bindingRequestKey(session, coordinates, env) {
452
456
  session.session_id,
453
457
  session.room_id,
454
458
  tokenDigest,
455
- new URL(env.LETAGENTS_API_URL?.trim() || "https://letagents.chat").origin,
459
+ roomApiOrigin(env.LETAGENTS_API_URL?.trim() || "https://letagents.chat"),
456
460
  ].join("\u0000");
457
461
  }
458
462
  async function verifyConfirmedBinding(session, coordinates, env, protocolVersion, timeoutMs) {
@@ -1,3 +1,4 @@
1
+ import { isLocalRoomApi } from "../../../../shared/room-api-origin.mjs";
1
2
  import { getDaemonToolExecutionContext } from "./daemon-tool-context.js";
2
3
  export const LETAGENTS_AGENT_SESSION_BEARER_ENV = "LETAGENTS_AGENT_SESSION_BEARER";
3
4
  export const LETAGENTS_SUPERVISED_BOUNDED_TURNS_ENV = "LETAGENTS_SUPERVISED_BOUNDED_TURNS";
@@ -40,6 +41,8 @@ export function getWorkerBearerRuntime() {
40
41
  }
41
42
  try {
42
43
  const parsed = new URL(apiUrl);
44
+ if (isLocalRoomApi(apiUrl) && supervised && profile === "supervised_room_turn" && !bearer)
45
+ return { mode: "supervised" };
43
46
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
44
47
  throw new Error("unsupported protocol");
45
48
  }
@@ -1,468 +1,2 @@
1
- import { normalizeRoutingHandle, normalizeRoutingSender, routingIdentityAliases, routingSenderAliasRows, routingSenderAliases, } from "../../shared/routing-aliases.mjs";
2
- import { parsePositivePgIntegerScopedId } from "./scoped-ids.js";
3
- /** Send-time human fallback only; never use this to re-route historical reads. */
4
- export function humanConversationFallback(input) {
5
- if (input.source !== "browser" || !input.publisherAccountId || input.publisherAgentKey || input.explicitlyAddressed)
6
- return null;
7
- const keys = [...new Set(input.registeredAgentKeys)];
8
- if (keys.length > 0 && keys.length <= 2)
9
- return { reason: "small_room", agentKeys: keys };
10
- if (keys.length > 2 && input.recentAgentKey && keys.includes(input.recentAgentKey)) {
11
- return { reason: "recent_conversation", agentKeys: [input.recentAgentKey] };
12
- }
13
- return null;
14
- }
15
- /**
16
- * Repository events can contain text written by any external contributor.
17
- * They remain visible room activity, but their text is never an instruction
18
- * channel for a managed local worker.
19
- */
20
- export function isUntrustedExternalActivationSource(source) {
21
- return normalizeSender(source) === "github";
22
- }
23
- const NON_AGENT_AT_HANDLES = new Set([
24
- "charset",
25
- "container",
26
- "counter-style",
27
- "font-face",
28
- "font-feature-values",
29
- "font-palette-values",
30
- "import",
31
- "keyframes",
32
- "layer",
33
- "media",
34
- "namespace",
35
- "page",
36
- "package",
37
- "property",
38
- "scope",
39
- "starting-style",
40
- "supports",
41
- "types",
42
- "viewport",
43
- ]);
44
- const TASK_OWNER_FOLLOW_UP_PATTERNS = [
45
- /^(?:ok(?:ay)?|right|cool|great|nice)?[\s,]*(?:try again|retry|rerun|re-run|continue|proceed|go ahead|carry on)\b/,
46
- /^(?:ok(?:ay)?|right|cool|great|nice)?[\s,]*(?:open|create|make|raise)\s+(?:a\s+)?pr\b/,
47
- /^(?:ok(?:ay)?|right|cool|great|nice)?[\s,]*(?:push|merge|ship|fix|test|run|update)\s+(?:it|that|this|again|tests?|the\s+tests?|ci)\b/,
48
- /\b(?:try again|open\s+(?:a\s+)?pr|create\s+(?:a\s+)?pr|make\s+(?:a\s+)?pr|push it|merge it|update it)\b/,
49
- ];
50
- export function attachAgentMessageActivation(message, identity, context = {}) {
51
- return {
52
- ...message,
53
- activation: {
54
- for_current_agent: decideAgentMessageActivation(message, identity, context),
55
- },
56
- };
57
- }
58
- /**
59
- * Send-time receipts are the activation authority. For a snapshot-bearing
60
- * message, a receipt activates and the absence of one is the durable
61
- * send-time "silent" — never re-promoted by re-running the router against
62
- * later task/thread/session state, which would create a second authority.
63
- * Only messages that predate routing snapshots keep the lazy per-reader
64
- * decision, so legacy backlog mentions still activate rotated sessions.
65
- */
66
- export function attachAgentMessageActivationsFromReceipts(messages, identity, receiptsMap, snapshotNumbers, context = {}) {
67
- if (!identity || identity.session_kind !== "worker") {
68
- return [...messages];
69
- }
70
- return messages.map((message) => {
71
- const msgIdStr = String(message.id ?? "");
72
- const msgNum = parsePositivePgIntegerScopedId(msgIdStr, "msg");
73
- const receipt = msgNum !== null ? receiptsMap.get(msgNum) || receiptsMap.get(msgIdStr) : null;
74
- if (receipt) {
75
- const reason = receipt.activation_reason;
76
- return {
77
- ...message,
78
- activation: {
79
- for_current_agent: {
80
- decision: "activate",
81
- reason: reason || "explicit_mention",
82
- addressed: true,
83
- },
84
- },
85
- };
86
- }
87
- // System failure rows are canonical silent control events. A routing
88
- // snapshot with no receipt must not erase their diagnostic reason.
89
- if (msgNum !== null
90
- && snapshotNumbers.has(msgNum)
91
- && normalizeSender(message.source) !== "managed_agent_failure") {
92
- return {
93
- ...message,
94
- activation: {
95
- for_current_agent: {
96
- decision: "silent",
97
- reason: "unaddressed",
98
- addressed: false,
99
- },
100
- },
101
- };
102
- }
103
- return attachAgentMessageActivation(message, identity, context);
104
- });
105
- }
106
- export function attachAgentMessageActivations(messages, identity, context = {}) {
107
- if (!identity || identity.session_kind !== "worker") {
108
- return [...messages];
109
- }
110
- return messages.map((message) => attachAgentMessageActivation(message, identity, context));
111
- }
112
- export function decideAgentMessageActivation(message, identity, context = {}) {
113
- if (normalizeSender(message.source) === "managed_agent_failure"
114
- || isUntrustedExternalActivationSource(message.source)) {
115
- return decision("silent", "system_event");
116
- }
117
- const messageId = normalizedString(message.id);
118
- const authoritativeLegacyDecision = context.authoritativeLegacyDecisions?.get(messageId);
119
- if (authoritativeLegacyDecision)
120
- return authoritativeLegacyDecision;
121
- if (context.selfMessageIds !== undefined
122
- ? context.selfMessageIds.has(messageId)
123
- : senderMatchesIdentity(message.sender, identity)) {
124
- return decision("silent", "self_message");
125
- }
126
- const mentions = extractMentionHandles(message.text);
127
- if (mentions.some(isBroadcastHandle)) {
128
- return decision("activate", "broadcast");
129
- }
130
- const authoritativeExplicitMentions = context.explicitMentionMessageIds;
131
- if (authoritativeExplicitMentions !== undefined
132
- ? authoritativeExplicitMentions.has(messageId)
133
- : mentions.some((mention) => activationIdentityAliases(identity).has(normalizeMentionIdentityHandle(mention)))) {
134
- return decision("activate", "explicit_mention");
135
- }
136
- if (hasBroadcastAddress(message.text)) {
137
- return decision("activate", "broadcast");
138
- }
139
- if (mentions.some(isLikelyAgentMentionHandle)) {
140
- return decision("silent", "explicit_other_mention");
141
- }
142
- const authoritativeThreadParticipantRootIds = context.threadParticipantRootIds;
143
- const authoritativeReplyTargets = context.replyTargetMessageIds;
144
- const hasAuthoritativeThreadMembership = isThreadReply(message)
145
- && authoritativeThreadParticipantRootIds !== undefined;
146
- if (authoritativeReplyTargets !== undefined
147
- ? authoritativeReplyTargets.has(messageId)
148
- : !hasAuthoritativeThreadMembership && senderMatchesIdentity(message.reply_to?.sender, identity)) {
149
- return decision("activate", "reply_target");
150
- }
151
- if (isAgentReplyTarget(message.reply_to) && !isThreadReply(message)) {
152
- return decision("silent", "other_reply_target");
153
- }
154
- if (isThreadReply(message)
155
- && (hasAuthoritativeThreadMembership
156
- ? authoritativeThreadParticipantRootIds.has(threadRootId(message))
157
- : threadParticipantsIncludeIdentity(message, identity))) {
158
- return decision("activate", "thread_participant");
159
- }
160
- const taskOwnerDecision = decideTaskOwnerActivation(message, identity, context);
161
- if (taskOwnerDecision) {
162
- return taskOwnerDecision;
163
- }
164
- return decision("unclear", "unaddressed");
165
- }
166
- function decision(decisionValue, reason) {
167
- return {
168
- decision: decisionValue,
169
- reason,
170
- addressed: decisionValue === "activate",
171
- };
172
- }
173
- function isThreadReply(message) {
174
- const ownId = normalizedString(message.id);
175
- const rootId = threadRootId(message);
176
- return Boolean(ownId && rootId && ownId !== rootId);
177
- }
178
- function threadRootId(message) {
179
- return normalizedString(message.thread_root_id) || normalizedString(message.thread?.root_message_id);
180
- }
181
- function isAgentReplyTarget(replyTo) {
182
- return normalizeSender(replyTo?.source) === "agent";
183
- }
184
- function threadParticipantsIncludeIdentity(message, identity) {
185
- const senders = [
186
- message.reply_to?.sender,
187
- message.thread?.latest_reply?.sender,
188
- ...(message.thread?.participants ?? []).map((participant) => participant.sender),
189
- ];
190
- return senders.some((sender) => senderMatchesIdentity(sender, identity));
191
- }
192
- function decideTaskOwnerActivation(message, identity, context) {
193
- if (!isTaskOwnerFollowUp(message.text)) {
194
- return null;
195
- }
196
- const owners = uniqueActiveWorkOwners(context.activeTaskLeases ?? []);
197
- if (owners.length !== 1) {
198
- return null;
199
- }
200
- const owner = owners[0];
201
- if (senderMatchesLeaseOwner(message.sender, owner)) {
202
- return null;
203
- }
204
- if (leaseOwnerMatchesIdentity(owner, identity)) {
205
- return decision("activate", "task_owner");
206
- }
207
- if (identityOverlapsLeaseOwner(identity, owner)) {
208
- return null;
209
- }
210
- return decision("silent", "task_owner");
211
- }
212
- function uniqueActiveWorkOwners(leases) {
213
- const ownersByKey = new Map();
214
- for (const lease of leases) {
215
- if (lease.kind !== "work" || lease.status !== "active")
216
- continue;
217
- const key = leaseOwnerKey(lease);
218
- if (!key)
219
- continue;
220
- ownersByKey.set(key, lease);
221
- }
222
- return [...ownersByKey.values()];
223
- }
224
- function leaseOwnerKey(lease) {
225
- const sessionId = normalizedString(lease.agent_session_id);
226
- if (sessionId)
227
- return `session:${sessionId}`;
228
- const instanceId = normalizedString(lease.agent_instance_id);
229
- const agentKey = normalizeSender(lease.agent_key);
230
- if (instanceId)
231
- return `instance:${agentKey}:${instanceId}`;
232
- if (agentKey)
233
- return `agent:${agentKey}`;
234
- const actorLabel = normalizeSender(lease.actor_label);
235
- return actorLabel ? `label:${actorLabel}` : null;
236
- }
237
- function leaseOwnerMatchesIdentity(lease, identity) {
238
- const leaseSessionId = normalizedString(lease.agent_session_id);
239
- if (leaseSessionId) {
240
- return leaseSessionId === normalizedString(identity.agent_session_id);
241
- }
242
- const leaseInstanceId = normalizedString(lease.agent_instance_id);
243
- if (leaseInstanceId) {
244
- return (leaseInstanceId === normalizedString(identity.agent_instance_id) &&
245
- normalizeSender(lease.agent_key) === normalizeSender(identity.agent_key));
246
- }
247
- const leaseAgentKey = normalizeSender(lease.agent_key);
248
- if (leaseAgentKey) {
249
- return leaseAgentKey === normalizeSender(identity.agent_key);
250
- }
251
- return senderMatchesIdentity(lease.actor_label, identity);
252
- }
253
- function senderMatchesLeaseOwner(sender, lease) {
254
- const normalizedSender = normalizeSender(sender);
255
- if (!normalizedSender)
256
- return false;
257
- return leaseOwnerAliases(lease).has(normalizedSender);
258
- }
259
- function identityOverlapsLeaseOwner(identity, lease) {
260
- const identityAliasesForOwner = aliasesForValues([
261
- identity.actor_label,
262
- identity.display_name,
263
- identity.agent_key,
264
- identity.agent_instance_id,
265
- identity.agent_session_id,
266
- ]);
267
- for (const ownerAlias of leaseOwnerAliases(lease)) {
268
- if (identityAliasesForOwner.has(ownerAlias)) {
269
- return true;
270
- }
271
- }
272
- return false;
273
- }
274
- function leaseOwnerAliases(lease) {
275
- return aliasesForValues([
276
- lease.actor_label,
277
- ...String(lease.actor_label || "").split("|"),
278
- lease.agent_key,
279
- lease.agent_instance_id,
280
- lease.agent_session_id,
281
- ]);
282
- }
283
- function aliasesForValues(values) {
284
- const aliases = new Set();
285
- for (const value of values) {
286
- const senderAlias = normalizeSender(value);
287
- if (senderAlias)
288
- aliases.add(senderAlias);
289
- const handleAlias = normalizeHandle(value);
290
- if (handleAlias)
291
- aliases.add(handleAlias);
292
- }
293
- return aliases;
294
- }
295
- function senderMatchesIdentity(sender, identity) {
296
- const normalizedSender = normalizeSender(sender);
297
- if (!normalizedSender)
298
- return false;
299
- const aliases = activationIdentityAliases(identity);
300
- if (aliases.has(normalizedSender))
301
- return true;
302
- return String(sender || "")
303
- .split("|")
304
- .some((part) => aliases.has(normalizeSender(part)));
305
- }
306
- export function activationIdentityAliases(identity) {
307
- return routingIdentityAliases(identity);
308
- }
309
- /** Canonical aliases materialized from a historical message sender. */
310
- export function activationSenderAliases(sender, segmentLimit = 16) {
311
- return routingSenderAliases(sender, segmentLimit);
312
- }
313
- /**
314
- * Resolve identity-bearing addresses against the complete active room
315
- * population. A display alias is authority only when it names one durable
316
- * agent key globally; account/provider filtering happens after this step.
317
- * Full historical sender labels take precedence over their pipe-delimited
318
- * compatibility segments.
319
- */
320
- export function resolveGloballyAddressedAgentKeys(message, identities) {
321
- return createGlobalAgentAddressResolver(identities)(message);
322
- }
323
- /**
324
- * Build the room-wide alias authority once, then resolve a page of legacy
325
- * messages without rebuilding every active worker alias set per message.
326
- */
327
- export function createGlobalAgentAddressResolver(identities, options = {}) {
328
- const keysByAlias = new Map();
329
- for (const identity of identities) {
330
- const key = normalizedString(identity.agent_key);
331
- if (!key)
332
- continue;
333
- for (const alias of activationIdentityAliases(identity)) {
334
- const keys = keysByAlias.get(alias) ?? new Set();
335
- keys.add(key);
336
- keysByAlias.set(alias, keys);
337
- }
338
- }
339
- const resolveExplicitMentionKey = (keys) => {
340
- if (!keys || keys.size === 0)
341
- return null;
342
- if (keys.size === 1)
343
- return keys.values().next().value;
344
- const preferredMatches = [...keys].filter((key) => options.preferredExplicitMentionAgentKeys?.has(key));
345
- if (preferredMatches.length !== 1)
346
- return null;
347
- const ownerScopes = new Set();
348
- for (const key of keys) {
349
- const scope = options.explicitMentionOwnerScopeByAgentKey?.get(key);
350
- if (!scope)
351
- return null;
352
- ownerScopes.add(scope);
353
- }
354
- return ownerScopes.size === 1 ? preferredMatches[0] : null;
355
- };
356
- return (message) => {
357
- const mentions = extractMentionHandles(message.text);
358
- const broadcast = mentions.some(isBroadcastHandle) || hasBroadcastAddress(message.text);
359
- const hasMention = mentions.some((mention) => !isBroadcastHandle(mention));
360
- const hasAgentMention = mentions.some(isLikelyAgentMentionHandle);
361
- const explicitMentionKeys = new Set();
362
- for (const mention of mentions) {
363
- if (isBroadcastHandle(mention))
364
- continue;
365
- const alias = normalizeMentionIdentityHandle(mention);
366
- if (!alias)
367
- continue;
368
- const resolvedKey = resolveExplicitMentionKey(keysByAlias.get(alias));
369
- if (resolvedKey)
370
- explicitMentionKeys.add(resolvedKey);
371
- }
372
- const replyTargetKeys = new Set();
373
- const replyAliases = normalizedString(message.reply_to?.source) === "agent"
374
- ? routingSenderAliasRows(message.reply_to?.sender)
375
- : [];
376
- const matchingKeys = (full) => {
377
- const keys = new Set();
378
- for (const row of replyAliases) {
379
- if (row.isFull !== full)
380
- continue;
381
- for (const key of keysByAlias.get(row.alias) ?? [])
382
- keys.add(key);
383
- }
384
- return keys;
385
- };
386
- const fullMatches = matchingKeys(true);
387
- const replyMatches = fullMatches.size > 0 ? fullMatches : matchingKeys(false);
388
- if (replyMatches.size === 1)
389
- replyTargetKeys.add(replyMatches.values().next().value);
390
- const senderKeys = new Set();
391
- const senderAliases = routingSenderAliasRows(message.sender);
392
- const senderMatchingKeys = (full) => {
393
- const keys = new Set();
394
- for (const row of senderAliases) {
395
- if (row.isFull !== full)
396
- continue;
397
- for (const key of keysByAlias.get(row.alias) ?? [])
398
- keys.add(key);
399
- }
400
- return keys;
401
- };
402
- const senderFullMatches = senderMatchingKeys(true);
403
- const senderMatches = senderFullMatches.size > 0
404
- ? senderFullMatches
405
- : senderMatchingKeys(false);
406
- if (senderMatches.size === 1)
407
- senderKeys.add(senderMatches.values().next().value);
408
- return {
409
- broadcast,
410
- hasMention,
411
- hasAgentMention,
412
- explicitMentionKeys,
413
- replyTargetKeys,
414
- senderKeys,
415
- };
416
- };
417
- }
418
- /** Shared legacy task-follow-up classifier used by API and desktop overlays. */
419
- export function isTaskOwnerFollowUpMessageText(text) {
420
- return isTaskOwnerFollowUp(text);
421
- }
422
- function extractMentionHandles(text) {
423
- const raw = typeof text === "string" ? text : "";
424
- const mentions = [];
425
- for (const match of raw.matchAll(/(^|[\s([{:;,])@([A-Za-z0-9][A-Za-z0-9_.:-]*(?:\/[A-Za-z0-9][A-Za-z0-9_.-]*)*)/g)) {
426
- mentions.push(match[2]);
427
- }
428
- return mentions;
429
- }
430
- function isBroadcastHandle(handle) {
431
- const normalized = normalizeHandle(handle);
432
- return normalized === "agents" || normalized === "everyone" || normalized === "room";
433
- }
434
- function isLikelyAgentMentionHandle(handle) {
435
- const raw = normalizedString(handle);
436
- if (!raw)
437
- return false;
438
- const normalized = raw.toLowerCase();
439
- const firstSegment = normalized.split("/", 1)[0].replace(/_/g, "-");
440
- if (normalized.startsWith("agent:"))
441
- return true;
442
- if (normalized.includes("/") && normalized === raw)
443
- return false;
444
- return !NON_AGENT_AT_HANDLES.has(firstSegment);
445
- }
446
- function hasBroadcastAddress(text) {
447
- const raw = typeof text === "string" ? text.toLowerCase() : "";
448
- return /\b(everyone|all agents|you guys|both of you|any agent|whoever owns this)\b/.test(raw);
449
- }
450
- function isTaskOwnerFollowUp(text) {
451
- const raw = typeof text === "string" ? text.trim().toLowerCase() : "";
452
- if (!raw)
453
- return false;
454
- return TASK_OWNER_FOLLOW_UP_PATTERNS.some((pattern) => pattern.test(raw));
455
- }
456
- function normalizedString(value) {
457
- return typeof value === "string" ? value.trim() : "";
458
- }
459
- function normalizeSender(value) {
460
- return normalizeRoutingSender(value);
461
- }
462
- function normalizeHandle(value) {
463
- return normalizeRoutingHandle(value);
464
- }
465
- function normalizeMentionIdentityHandle(value) {
466
- const normalized = normalizeHandle(value);
467
- return normalized.startsWith("agent:") ? normalized.slice("agent:".length) : normalized;
468
- }
1
+ // API and local supervision use the same pure routing rules.
2
+ export * from "../../shared/activation-routing.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "letagents",
3
- "version": "0.12.20",
3
+ "version": "0.12.21",
4
4
  "description": "Let Agents Chat — MCP server for AI agent communication",
5
5
  "type": "module",
6
6
  "main": "dist/mcp/server.js",
@@ -0,0 +1,139 @@
1
+ export type AgentMessageActivationDecision = "activate" | "silent" | "unclear";
2
+ export type AgentMessageActivationReason = "self_message" | "explicit_mention" | "explicit_other_mention" | "broadcast" | "reply_target" | "other_reply_target" | "thread_participant" | "task_owner" | "small_room" | "recent_conversation" | "system_event" | "unaddressed";
3
+ /** Send-time human fallback only; never use this to re-route historical reads. */
4
+ export declare function humanConversationFallback(input: {
5
+ source: string | null;
6
+ publisherAccountId: string | null;
7
+ publisherAgentKey: string | null;
8
+ explicitlyAddressed: boolean;
9
+ registeredAgentKeys: readonly string[];
10
+ recentAgentKey?: string | null;
11
+ }): {
12
+ reason: "small_room" | "recent_conversation";
13
+ agentKeys: string[];
14
+ } | null;
15
+ /**
16
+ * Repository events can contain text written by any external contributor.
17
+ * They remain visible room activity, but their text is never an instruction
18
+ * channel for a managed local worker.
19
+ */
20
+ export declare function isUntrustedExternalActivationSource(source: unknown): boolean;
21
+ export interface AgentMessageActivation {
22
+ for_current_agent: {
23
+ decision: AgentMessageActivationDecision;
24
+ reason: AgentMessageActivationReason;
25
+ addressed: boolean;
26
+ };
27
+ }
28
+ type MessageLike = {
29
+ id?: unknown;
30
+ sender?: unknown;
31
+ text?: unknown;
32
+ source?: unknown;
33
+ thread_root_id?: unknown;
34
+ thread?: {
35
+ root_message_id?: unknown;
36
+ participants?: Array<{
37
+ sender?: unknown;
38
+ }> | null;
39
+ latest_reply?: {
40
+ sender?: unknown;
41
+ } | null;
42
+ } | null;
43
+ reply_to?: {
44
+ sender?: unknown;
45
+ source?: unknown;
46
+ } | null;
47
+ };
48
+ export type ActivationIdentity = {
49
+ actor_label: string;
50
+ agent_key: string;
51
+ agent_instance_id: string | null;
52
+ agent_session_id: string | null;
53
+ display_name: string;
54
+ session_kind: string;
55
+ };
56
+ type ActivationTaskLeaseLike = {
57
+ kind: string;
58
+ status: string;
59
+ actor_label: string;
60
+ agent_key: string;
61
+ agent_instance_id: string | null;
62
+ agent_session_id: string | null;
63
+ };
64
+ export type AgentMessageActivationContext = {
65
+ activeTaskLeases?: readonly ActivationTaskLeaseLike[];
66
+ /** Legacy messages authored by this exact durable identity. */
67
+ selfMessageIds?: ReadonlySet<string>;
68
+ /** Thread roots whose routing projection contains this exact identity. */
69
+ threadParticipantRootIds?: ReadonlySet<string>;
70
+ /** Legacy messages whose globally resolved mention names this identity. */
71
+ explicitMentionMessageIds?: ReadonlySet<string>;
72
+ /** Legacy messages whose globally resolved reply target names this identity. */
73
+ replyTargetMessageIds?: ReadonlySet<string>;
74
+ /** Complete legacy decisions supplied by the room-global routing authority. */
75
+ authoritativeLegacyDecisions?: ReadonlyMap<string, AgentMessageActivation["for_current_agent"]>;
76
+ };
77
+ export declare function attachAgentMessageActivation<T extends MessageLike>(message: T, identity: ActivationIdentity, context?: AgentMessageActivationContext): T & {
78
+ activation: AgentMessageActivation;
79
+ };
80
+ /**
81
+ * Send-time receipts are the activation authority. For a snapshot-bearing
82
+ * message, a receipt activates and the absence of one is the durable
83
+ * send-time "silent" — never re-promoted by re-running the router against
84
+ * later task/thread/session state, which would create a second authority.
85
+ * Only messages that predate routing snapshots keep the lazy per-reader
86
+ * decision, so legacy backlog mentions still activate rotated sessions.
87
+ */
88
+ export declare function attachAgentMessageActivationsFromReceipts<T extends MessageLike>(messages: readonly T[], identity: ActivationIdentity | null, receiptsMap: ReadonlyMap<number | string, {
89
+ activation_reason: string;
90
+ }>, snapshotNumbers: ReadonlySet<number>, context?: AgentMessageActivationContext): T[] | Array<T & {
91
+ activation: AgentMessageActivation;
92
+ }>;
93
+ export declare function attachAgentMessageActivations<T extends MessageLike>(messages: readonly T[], identity: ActivationIdentity | null, context?: AgentMessageActivationContext): T[] | Array<T & {
94
+ activation: AgentMessageActivation;
95
+ }>;
96
+ export declare function decideAgentMessageActivation(message: MessageLike, identity: ActivationIdentity, context?: AgentMessageActivationContext): AgentMessageActivation["for_current_agent"];
97
+ export declare function activationIdentityAliases(identity: ActivationIdentity): Set<string>;
98
+ /** Canonical aliases materialized from a historical message sender. */
99
+ export declare function activationSenderAliases(sender: unknown, segmentLimit?: number): Set<string>;
100
+ /**
101
+ * Resolve identity-bearing addresses against the complete active room
102
+ * population. A display alias is authority only when it names one durable
103
+ * agent key globally; account/provider filtering happens after this step.
104
+ * Full historical sender labels take precedence over their pipe-delimited
105
+ * compatibility segments.
106
+ */
107
+ export declare function resolveGloballyAddressedAgentKeys(message: Pick<MessageLike, "text" | "reply_to">, identities: readonly ActivationIdentity[]): {
108
+ explicitMentionKeys: Set<string>;
109
+ replyTargetKeys: Set<string>;
110
+ };
111
+ export interface GlobalAgentAddressResolverOptions {
112
+ /**
113
+ * Break a duplicate friendly-name tie only when exactly one of the durable
114
+ * identities is currently reachable. Canonical agent-key aliases remain
115
+ * unique without this hint, and multiple reachable matches still fail
116
+ * closed.
117
+ */
118
+ preferredExplicitMentionAgentKeys?: ReadonlySet<string>;
119
+ /**
120
+ * Stable ownership boundary for each preferred key. Reachability may only
121
+ * break a tie when every colliding durable key belongs to the same scope.
122
+ */
123
+ explicitMentionOwnerScopeByAgentKey?: ReadonlyMap<string, string>;
124
+ }
125
+ /**
126
+ * Build the room-wide alias authority once, then resolve a page of legacy
127
+ * messages without rebuilding every active worker alias set per message.
128
+ */
129
+ export declare function createGlobalAgentAddressResolver(identities: readonly ActivationIdentity[], options?: GlobalAgentAddressResolverOptions): (message: Pick<MessageLike, "text" | "reply_to"> & Partial<Pick<MessageLike, "sender">>) => {
130
+ broadcast: boolean;
131
+ hasMention: boolean;
132
+ hasAgentMention: boolean;
133
+ explicitMentionKeys: Set<string>;
134
+ replyTargetKeys: Set<string>;
135
+ senderKeys: Set<string>;
136
+ };
137
+ /** Shared legacy task-follow-up classifier used by API and desktop overlays. */
138
+ export declare function isTaskOwnerFollowUpMessageText(text: unknown): boolean;
139
+ export {};