letagents 0.12.32 → 0.12.33

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.
@@ -260,7 +260,7 @@ export function registerBoardIntentTools(server) {
260
260
  }
261
261
  });
262
262
  server.tool("list_board_intents", "List Board Manager intents for this room, optionally filtered by status.", {
263
- status: z.enum(["pending", "approved", "denied", "expired", "used"]).optional(),
263
+ status: z.enum(["pending", "approved", "denied", "expired", "used", "superseded"]).optional(),
264
264
  room_id: z.string().optional().describe("Canonical room ID. Defaults to current room."),
265
265
  }, async ({ status, room_id }) => {
266
266
  const targetRoomId = resolveCanonicalRoomId(room_id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "letagents",
3
- "version": "0.12.32",
3
+ "version": "0.12.33",
4
4
  "description": "Let Agents Chat \u2014 MCP server for AI agent communication",
5
5
  "type": "module",
6
6
  "main": "dist/mcp/server.js",
@@ -22,4 +22,6 @@ export function createKnowledgeRecord(room_id: string, type: KnowledgeType, inpu
22
22
  export function reviseKnowledgeRecord(record: KnowledgeRecord, input: KnowledgeRevisionInput, actor: KnowledgeActor, now?: string): KnowledgeRecord;
23
23
  export function assertKnowledgeReplay(existing: KnowledgeRecord, candidate: KnowledgeRecord): void;
24
24
 
25
+ export function attentionResponseHandle(agentKey: string | null | undefined): string;
25
26
  export function formatAttentionResponse(record: KnowledgeRecord): string;
27
+ export function attentionResponseDisplayText(text: string, agentName: (handle: string) => string | null | undefined): string | null;
@@ -68,7 +68,38 @@ export function assertKnowledgeReplay(existing, candidate) {
68
68
  }
69
69
  }
70
70
 
71
+ /** The `@agent:` handle an answer uses to wake the agent that asked. */
72
+ export function attentionResponseHandle(agentKey) {
73
+ return String(agentKey ?? '').trim().replace(/[A-Z]/g, c => c.toLowerCase()).replace(/[^a-z0-9_.:/-]+/g, '');
74
+ }
75
+
71
76
  export function formatAttentionResponse(record) {
72
- const handle = record.author.kind === 'agent' ? record.author.id.trim().replace(/[A-Z]/g, c => c.toLowerCase()).replace(/[^a-z0-9_.:/-]+/g, '') : '';
77
+ const handle = record.author.kind === 'agent' ? attentionResponseHandle(record.author.id) : '';
73
78
  return `${handle ? `@agent:${handle}\n\n` : ''}Human response (${record.id}):\n\n${record.response.body}`;
74
79
  }
80
+
81
+ const ATTENTION_RESPONSE_TEXT = /^(?:@agent:([a-z0-9_.:/-]+)\n\n)?Human response \([a-zA-Z0-9_-]{8,80}\):\n\n([\s\S]+)$/;
82
+ // A room display name used as a mention: never markdown, never spaces, and
83
+ // no underscore that could open emphasis (leading, or doubled anywhere).
84
+ const MENTION_NAME = /^(?!_)(?!.*__)[A-Za-z0-9._:-]{1,64}$/;
85
+ // Answers that open with a block (fence, list, heading, quote, table, rule or
86
+ // indented code) keep it as a block instead of joining the mention's line.
87
+ const OPENS_WITH_BLOCK = /^(?: {4}|\t| {0,3}(?:`{3}|~{3}|[-*+](?:\s|$)|\d{1,9}[.)](?:\s|$)|#{1,6}(?:\s|$)|>|\||(?:[-*_] *){3,}$))/;
88
+
89
+ /**
90
+ * What people see for a message formatAttentionResponse wrote. The handle and
91
+ * request id route the answer to the agent that asked; the room reads it as a
92
+ * reply to that agent by name, or to a neutral @agent when the room no longer
93
+ * lists it (the visible mention is the record of who was woken). Null for any
94
+ * other text.
95
+ */
96
+ export function attentionResponseDisplayText(text, agentName) {
97
+ const match = ATTENTION_RESPONSE_TEXT.exec(typeof text === 'string' ? text : '');
98
+ if (!match) return null;
99
+ const answer = match[2];
100
+ if (!match[1]) return answer;
101
+ const name = String(agentName(match[1]) ?? '').trim();
102
+ const mention = `@${MENTION_NAME.test(name) ? name : 'agent'}`;
103
+ // trimEnd: a CRLF line keeps its \r, and trailing space must not hide a rule.
104
+ return OPENS_WITH_BLOCK.test(answer.split('\n', 1)[0].trimEnd()) ? `${mention}\n\n${answer}` : `${mention} ${answer}`;
105
+ }
@@ -0,0 +1,7 @@
1
+ export const NO_REPLY_FAILURE: Readonly<{
2
+ outputLimit: "The model hit its output limit before writing a reply.";
3
+ emptyAnswer: "The model finished without writing a reply.";
4
+ contentFilter: "The model provider's content filter stopped the reply before any text was written.";
5
+ }>;
6
+ export type NoReplyFailureKind = keyof typeof NO_REPLY_FAILURE;
7
+ export function noReplyFailureKind(detail: unknown): NoReplyFailureKind | null;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Why a completed provider turn ended without a room reply. The Open Model
3
+ * adapter reports these as the exact failure detail, and task continuity
4
+ * reads them back to decide whether a follow-up turn can help.
5
+ */
6
+ export const NO_REPLY_FAILURE = Object.freeze({
7
+ outputLimit: "The model hit its output limit before writing a reply.",
8
+ emptyAnswer: "The model finished without writing a reply.",
9
+ contentFilter: "The model provider's content filter stopped the reply before any text was written.",
10
+ });
11
+
12
+ export function noReplyFailureKind(detail) {
13
+ const text = typeof detail === "string" ? detail.trim() : "";
14
+ return Object.keys(NO_REPLY_FAILURE).find((kind) => NO_REPLY_FAILURE[kind] === text) ?? null;
15
+ }