@abraca/mcp 1.8.0 → 1.8.1

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.
@@ -3761,7 +3761,7 @@ const propertyKeyTypes = new Set([
3761
3761
  "number",
3762
3762
  "symbol"
3763
3763
  ]);
3764
- function escapeRegex(str) {
3764
+ function escapeRegex$1(str) {
3765
3765
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3766
3766
  }
3767
3767
  function clone(inst, def, params) {
@@ -4472,7 +4472,7 @@ const $ZodCheckUpperCase = /* @__PURE__ */ $constructor("$ZodCheckUpperCase", (i
4472
4472
  });
4473
4473
  const $ZodCheckIncludes = /* @__PURE__ */ $constructor("$ZodCheckIncludes", (inst, def) => {
4474
4474
  $ZodCheck.init(inst, def);
4475
- const escapedRegex = escapeRegex(def.includes);
4475
+ const escapedRegex = escapeRegex$1(def.includes);
4476
4476
  const pattern = new RegExp(typeof def.position === "number" ? `^.{${def.position}}${escapedRegex}` : escapedRegex);
4477
4477
  def.pattern = pattern;
4478
4478
  inst._zod.onattach.push((inst) => {
@@ -4495,7 +4495,7 @@ const $ZodCheckIncludes = /* @__PURE__ */ $constructor("$ZodCheckIncludes", (ins
4495
4495
  });
4496
4496
  const $ZodCheckStartsWith = /* @__PURE__ */ $constructor("$ZodCheckStartsWith", (inst, def) => {
4497
4497
  $ZodCheck.init(inst, def);
4498
- const pattern = new RegExp(`^${escapeRegex(def.prefix)}.*`);
4498
+ const pattern = new RegExp(`^${escapeRegex$1(def.prefix)}.*`);
4499
4499
  def.pattern ?? (def.pattern = pattern);
4500
4500
  inst._zod.onattach.push((inst) => {
4501
4501
  const bag = inst._zod.bag;
@@ -4517,7 +4517,7 @@ const $ZodCheckStartsWith = /* @__PURE__ */ $constructor("$ZodCheckStartsWith",
4517
4517
  });
4518
4518
  const $ZodCheckEndsWith = /* @__PURE__ */ $constructor("$ZodCheckEndsWith", (inst, def) => {
4519
4519
  $ZodCheck.init(inst, def);
4520
- const pattern = new RegExp(`.*${escapeRegex(def.suffix)}$`);
4520
+ const pattern = new RegExp(`.*${escapeRegex$1(def.suffix)}$`);
4521
4521
  def.pattern ?? (def.pattern = pattern);
4522
4522
  inst._zod.onattach.push((inst) => {
4523
4523
  const bag = inst._zod.bag;
@@ -5549,7 +5549,7 @@ const $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
5549
5549
  const values = getEnumValues(def.entries);
5550
5550
  const valuesSet = new Set(values);
5551
5551
  inst._zod.values = valuesSet;
5552
- inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex(o) : o.toString()).join("|")})$`);
5552
+ inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex$1(o) : o.toString()).join("|")})$`);
5553
5553
  inst._zod.parse = (payload, _ctx) => {
5554
5554
  const input = payload.value;
5555
5555
  if (valuesSet.has(input)) return payload;
@@ -5567,7 +5567,7 @@ const $ZodLiteral = /* @__PURE__ */ $constructor("$ZodLiteral", (inst, def) => {
5567
5567
  if (def.values.length === 0) throw new Error("Cannot create literal schema with no valid values");
5568
5568
  const values = new Set(def.values);
5569
5569
  inst._zod.values = values;
5570
- inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex(o) : o ? escapeRegex(o.toString()) : String(o)).join("|")})$`);
5570
+ inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex$1(o) : o ? escapeRegex$1(o.toString()) : String(o)).join("|")})$`);
5571
5571
  inst._zod.parse = (payload, _ctx) => {
5572
5572
  const input = payload.value;
5573
5573
  if (values.has(input)) return payload;
@@ -19969,6 +19969,47 @@ function signChallenge(challengeB64, privateKey) {
19969
19969
  return toBase64url(sign(challenge, privateKey));
19970
19970
  }
19971
19971
 
19972
+ //#endregion
19973
+ //#region packages/mcp/src/mentions.ts
19974
+ /**
19975
+ * Mention parsing for chat messages.
19976
+ *
19977
+ * Recognizes `@alias` tokens (case-insensitive, word-boundary) so the agent
19978
+ * can decide whether a group-chat message is directed at it.
19979
+ */
19980
+ /** Escape regex metacharacters in an alias string. */
19981
+ function escapeRegex(s) {
19982
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
19983
+ }
19984
+ /**
19985
+ * Build a regex that matches `@<alias>` for any of the given aliases.
19986
+ * Requires a non-word char (or start) before `@` and a word boundary after the alias
19987
+ * so `@Claude` matches but `email@claudesomething` does not.
19988
+ */
19989
+ function buildMentionRegex(aliases) {
19990
+ const cleaned = aliases.map((a) => a.trim()).filter((a) => a.length > 0);
19991
+ if (cleaned.length === 0) return null;
19992
+ const alt = cleaned.map(escapeRegex).join("|");
19993
+ return new RegExp(`(?:^|[^\\w@])@(?:${alt})\\b`, "i");
19994
+ }
19995
+ /** Returns true if `text` contains `@alias` for any alias (case-insensitive). */
19996
+ function containsMention(text, aliases) {
19997
+ const re = buildMentionRegex(aliases);
19998
+ if (!re) return false;
19999
+ return re.test(text);
20000
+ }
20001
+ /**
20002
+ * Remove `@alias` tokens from the text. Leaves surrounding whitespace tidy so
20003
+ * the cleaned prompt reads naturally (e.g. `"@Claude help"` → `"help"`).
20004
+ */
20005
+ function stripMention(text, aliases) {
20006
+ const cleaned = aliases.map((a) => a.trim()).filter((a) => a.length > 0);
20007
+ if (cleaned.length === 0) return text;
20008
+ const alt = cleaned.map(escapeRegex).join("|");
20009
+ const re = new RegExp(`(^|\\s)@(?:${alt})\\b[,:]?\\s*`, "gi");
20010
+ return text.replace(re, (_m, lead) => lead ? " " : "").replace(/\s{2,}/g, " ").trim();
20011
+ }
20012
+
19972
20013
  //#endregion
19973
20014
  //#region packages/mcp/src/server.ts
19974
20015
  /**
@@ -19979,7 +20020,10 @@ function signChallenge(challengeB64, privateKey) {
19979
20020
  * Use switchSpace(docId) to change the active space.
19980
20021
  */
19981
20022
  const IDLE_TIMEOUT_MS = 300 * 1e3;
19982
- var AbracadabraMCPServer = class {
20023
+ var AbracadabraMCPServer = class AbracadabraMCPServer {
20024
+ static {
20025
+ this.TOOL_HISTORY_MAX = 20;
20026
+ }
19983
20027
  constructor(config) {
19984
20028
  this._serverInfo = null;
19985
20029
  this._rootDocId = null;
@@ -19996,6 +20040,7 @@ var AbracadabraMCPServer = class {
19996
20040
  this._typingInterval = null;
19997
20041
  this._lastChatChannel = null;
19998
20042
  this._signFn = null;
20043
+ this._toolHistory = [];
19999
20044
  this.config = config;
20000
20045
  this.client = new _abraca_dabra.AbracadabraClient({
20001
20046
  url: config.url,
@@ -20008,6 +20053,14 @@ var AbracadabraMCPServer = class {
20008
20053
  get agentColor() {
20009
20054
  return this.config.agentColor || "hsl(270, 80%, 60%)";
20010
20055
  }
20056
+ get triggerMode() {
20057
+ return this.config.triggerMode ?? "mention+task";
20058
+ }
20059
+ get mentionAliases() {
20060
+ const explicit = this.config.mentionAliases?.filter((a) => a.trim().length > 0);
20061
+ if (explicit && explicit.length > 0) return explicit;
20062
+ return [this.agentName];
20063
+ }
20011
20064
  get serverInfo() {
20012
20065
  return this._serverInfo;
20013
20066
  }
@@ -20048,7 +20101,7 @@ var AbracadabraMCPServer = class {
20048
20101
  await this.client.loginWithKey(keypair.publicKeyB64, signFn);
20049
20102
  } else throw err;
20050
20103
  }
20051
- console.error(`[abracadabra-mcp] Authenticated as ${this.agentName} (${keypair.publicKeyB64.slice(0, 12)}...)`);
20104
+ console.error(`[abracadabra-mcp] Authenticated as ${this.agentName} (pubkey=${keypair.publicKeyB64})`);
20052
20105
  this._serverInfo = await this.client.serverInfo();
20053
20106
  let initialDocId = this._serverInfo.index_doc_id ?? null;
20054
20107
  try {
@@ -20115,6 +20168,8 @@ var AbracadabraMCPServer = class {
20115
20168
  provider.awareness.setLocalStateField("status", null);
20116
20169
  provider.awareness.setLocalStateField("activeToolCall", null);
20117
20170
  provider.awareness.setLocalStateField("statusContext", null);
20171
+ provider.awareness.setLocalStateField("turnId", null);
20172
+ provider.awareness.setLocalStateField("toolHistory", []);
20118
20173
  const conn = {
20119
20174
  doc,
20120
20175
  provider,
@@ -20238,6 +20293,7 @@ var AbracadabraMCPServer = class {
20238
20293
  _observeRootAwareness(provider) {
20239
20294
  const selfId = provider.awareness.clientID;
20240
20295
  provider.awareness.on("change", () => {
20296
+ if (this.triggerMode === "mention") return;
20241
20297
  const states = provider.awareness.getStates();
20242
20298
  for (const [clientId, state] of states) {
20243
20299
  if (clientId === selfId) continue;
@@ -20250,6 +20306,7 @@ var AbracadabraMCPServer = class {
20250
20306
  const user = state["user"];
20251
20307
  const senderName = user && typeof user === "object" && typeof user.name === "string" ? user.name : "Unknown";
20252
20308
  console.error(`[abracadabra-mcp] Handling ai:task id=${id} from ${senderName}: ${text.slice(0, 80)}`);
20309
+ this._beginTurn();
20253
20310
  this.setAutoStatus("thinking");
20254
20311
  this._dispatchAiTask({
20255
20312
  id,
@@ -20312,9 +20369,28 @@ var AbracadabraMCPServer = class {
20312
20369
  if (data.sender_id && data.sender_id === this._userId) return;
20313
20370
  const channel = data.channel;
20314
20371
  const docId = channel?.startsWith("group:") ? channel.slice(6) : "";
20315
- if (channel?.startsWith("dm:")) {
20372
+ const isDM = channel?.startsWith("dm:") ?? false;
20373
+ const isGroup = channel?.startsWith("group:") ?? false;
20374
+ if (isDM) {
20316
20375
  const parts = channel.split(":");
20317
- if (parts.length === 3 && parts[1] !== this._userId && parts[2] !== this._userId) return;
20376
+ if (parts.length === 3 && parts[1] !== this._userId && parts[2] !== this._userId) {
20377
+ console.error(`[abracadabra-mcp] Dropping DM: agent _userId=${this._userId} not in channel parts=[${parts[1]}, ${parts[2]}] — channel='${channel}'. The dashboard's awareness likely points at a stale Claude identity.`);
20378
+ return;
20379
+ }
20380
+ }
20381
+ const mode = this.triggerMode;
20382
+ const content = typeof data.content === "string" ? data.content : "";
20383
+ let dispatchContent = content;
20384
+ if (isGroup) {
20385
+ if (mode === "task") return;
20386
+ if (mode === "mention" || mode === "mention+task") {
20387
+ const aliases = this.mentionAliases;
20388
+ if (!containsMention(content, aliases)) {
20389
+ console.error(`[abracadabra-mcp] skipped message on ${channel} — no @mention for ${aliases.join("|")}`);
20390
+ return;
20391
+ }
20392
+ dispatchContent = stripMention(content, aliases) || content;
20393
+ }
20318
20394
  }
20319
20395
  if (channel) {
20320
20396
  const rootProvider = this._activeConnection?.provider;
@@ -20324,14 +20400,13 @@ var AbracadabraMCPServer = class {
20324
20400
  timestamp: Math.floor(Date.now() / 1e3)
20325
20401
  }));
20326
20402
  this._lastChatChannel = channel;
20327
- this.sendTypingIndicator(channel);
20328
- this._startTypingInterval(channel);
20329
20403
  }
20404
+ this._beginTurn();
20330
20405
  this.setAutoStatus("thinking");
20331
20406
  await this._serverRef.notification({
20332
20407
  method: "notifications/claude/channel",
20333
20408
  params: {
20334
- content: data.content ?? "",
20409
+ content: dispatchContent,
20335
20410
  instructions: `You MUST use send_chat_message with channel="${channel ?? ""}" for ALL responses — both progress updates and final answers. The user CANNOT see plain text output; they only see messages sent via send_chat_message. When doing multi-step work, send brief status updates via send_chat_message (e.g. "Looking into that..." or "Found it, writing up results...") so the user knows you're working. Never output plain text as a substitute for send_chat_message.`,
20336
20411
  meta: {
20337
20412
  source: "abracadabra",
@@ -20362,13 +20437,35 @@ var AbracadabraMCPServer = class {
20362
20437
  if (docId !== void 0) provider.awareness.setLocalStateField("docId", docId);
20363
20438
  const context = status ? statusContext !== void 0 ? statusContext : this._lastChatChannel : null;
20364
20439
  provider.awareness.setLocalStateField("statusContext", context ?? null);
20365
- if (!status) this._stopTypingInterval();
20440
+ if (!status) {
20441
+ this._stopTypingInterval();
20442
+ provider.awareness.setLocalStateField("activeToolCall", null);
20443
+ provider.awareness.setLocalStateField("turnId", null);
20444
+ this._toolHistory = [];
20445
+ provider.awareness.setLocalStateField("toolHistory", []);
20446
+ }
20366
20447
  if (status) this._statusClearTimer = setTimeout(() => {
20367
20448
  provider.awareness.setLocalStateField("status", null);
20368
20449
  provider.awareness.setLocalStateField("activeToolCall", null);
20369
20450
  provider.awareness.setLocalStateField("statusContext", null);
20451
+ provider.awareness.setLocalStateField("turnId", null);
20452
+ this._toolHistory = [];
20453
+ provider.awareness.setLocalStateField("toolHistory", []);
20370
20454
  this._stopTypingInterval();
20371
- }, 3e4);
20455
+ }, 1e4);
20456
+ }
20457
+ /**
20458
+ * Start a new agent turn. Mints a fresh UUID and writes it to awareness so
20459
+ * the dashboard can gate the incantation on "there is an active turn",
20460
+ * decoupled from the (racier) status field. Called from chat arrival and
20461
+ * ai:task dispatch right before `setAutoStatus('thinking')`.
20462
+ */
20463
+ _beginTurn() {
20464
+ const provider = this._activeConnection?.provider;
20465
+ if (!provider) return;
20466
+ this._toolHistory = [];
20467
+ provider.awareness.setLocalStateField("toolHistory", []);
20468
+ provider.awareness.setLocalStateField("turnId", crypto.randomUUID());
20372
20469
  }
20373
20470
  /** Re-send typing indicator every 2s so dashboard keeps showing it (expires at 3s). */
20374
20471
  _startTypingInterval(channel) {
@@ -20386,10 +20483,28 @@ var AbracadabraMCPServer = class {
20386
20483
  }
20387
20484
  /**
20388
20485
  * Broadcast which tool the agent is currently executing.
20389
- * Dashboard renders this as a ChatTool indicator.
20486
+ *
20487
+ * Renders as a ChatTool pill on the dashboard. On non-null calls, the tool
20488
+ * is also appended to `toolHistory` (capped at TOOL_HISTORY_MAX) and written
20489
+ * to awareness so the dashboard's inline trace can show the turn's recent
20490
+ * activity. Tools do NOT clear (`setActiveToolCall(null)`) on completion —
20491
+ * the pill stays until the next tool replaces it or `setAutoStatus(null)`
20492
+ * flushes the turn. This keeps pills visible long enough to see.
20390
20493
  */
20391
20494
  setActiveToolCall(toolCall) {
20392
- this._activeConnection?.provider?.awareness.setLocalStateField("activeToolCall", toolCall);
20495
+ const provider = this._activeConnection?.provider;
20496
+ if (!provider) return;
20497
+ provider.awareness.setLocalStateField("activeToolCall", toolCall);
20498
+ if (toolCall) {
20499
+ this._toolHistory.push({
20500
+ tool: toolCall.name,
20501
+ target: toolCall.target,
20502
+ ts: Date.now(),
20503
+ channel: this._lastChatChannel
20504
+ });
20505
+ if (this._toolHistory.length > AbracadabraMCPServer.TOOL_HISTORY_MAX) this._toolHistory.splice(0, this._toolHistory.length - AbracadabraMCPServer.TOOL_HISTORY_MAX);
20506
+ provider.awareness.setLocalStateField("toolHistory", [...this._toolHistory]);
20507
+ }
20393
20508
  }
20394
20509
  /**
20395
20510
  * Send a typing indicator to a chat channel.
@@ -20420,8 +20535,11 @@ var AbracadabraMCPServer = class {
20420
20535
  conn.provider.awareness.setLocalStateField("status", null);
20421
20536
  conn.provider.awareness.setLocalStateField("activeToolCall", null);
20422
20537
  conn.provider.awareness.setLocalStateField("statusContext", null);
20538
+ conn.provider.awareness.setLocalStateField("turnId", null);
20539
+ conn.provider.awareness.setLocalStateField("toolHistory", []);
20423
20540
  conn.provider.destroy();
20424
20541
  }
20542
+ this._toolHistory = [];
20425
20543
  this._spaceConnections.clear();
20426
20544
  this._activeConnection = null;
20427
20545
  console.error("[abracadabra-mcp] Shutdown complete");
@@ -21237,16 +21355,12 @@ function registerTreeTools(mcp, server) {
21237
21355
  server.setAutoStatus("reading");
21238
21356
  server.setActiveToolCall({ name: "list_documents" });
21239
21357
  const treeMap = server.getTreeMap();
21240
- if (!treeMap) {
21241
- server.setActiveToolCall(null);
21242
- return { content: [{
21243
- type: "text",
21244
- text: "Not connected"
21245
- }] };
21246
- }
21358
+ if (!treeMap) return { content: [{
21359
+ type: "text",
21360
+ text: "Not connected"
21361
+ }] };
21247
21362
  const targetId = normalizeRootId(parentId, server);
21248
21363
  const children = childrenOf$1(readEntries$1(treeMap), targetId);
21249
- server.setActiveToolCall(null);
21250
21364
  return { content: [{
21251
21365
  type: "text",
21252
21366
  text: JSON.stringify(children, null, 2)
@@ -21259,17 +21373,13 @@ function registerTreeTools(mcp, server) {
21259
21373
  server.setAutoStatus("reading");
21260
21374
  server.setActiveToolCall({ name: "get_document_tree" });
21261
21375
  const treeMap = server.getTreeMap();
21262
- if (!treeMap) {
21263
- server.setActiveToolCall(null);
21264
- return { content: [{
21265
- type: "text",
21266
- text: "Not connected"
21267
- }] };
21268
- }
21376
+ if (!treeMap) return { content: [{
21377
+ type: "text",
21378
+ text: "Not connected"
21379
+ }] };
21269
21380
  const targetId = normalizeRootId(rootId, server);
21270
21381
  const maxDepth = depth ?? 3;
21271
21382
  const tree = buildTree$1(readEntries$1(treeMap), targetId, maxDepth);
21272
- server.setActiveToolCall(null);
21273
21383
  return { content: [{
21274
21384
  type: "text",
21275
21385
  text: JSON.stringify(tree, null, 2)
@@ -21285,13 +21395,10 @@ function registerTreeTools(mcp, server) {
21285
21395
  target: query
21286
21396
  });
21287
21397
  const treeMap = server.getTreeMap();
21288
- if (!treeMap) {
21289
- server.setActiveToolCall(null);
21290
- return { content: [{
21291
- type: "text",
21292
- text: "Not connected"
21293
- }] };
21294
- }
21398
+ if (!treeMap) return { content: [{
21399
+ type: "text",
21400
+ text: "Not connected"
21401
+ }] };
21295
21402
  const entries = readEntries$1(treeMap);
21296
21403
  const lowerQuery = query.toLowerCase();
21297
21404
  const normalizedRoot = normalizeRootId(rootId, server);
@@ -21316,7 +21423,6 @@ function registerTreeTools(mcp, server) {
21316
21423
  path
21317
21424
  };
21318
21425
  });
21319
- server.setActiveToolCall(null);
21320
21426
  if (results.length === 0) return { content: [{
21321
21427
  type: "text",
21322
21428
  text: `No documents found matching "${query}". Try get_document_tree to see the full hierarchy.`
@@ -21330,7 +21436,7 @@ function registerTreeTools(mcp, server) {
21330
21436
  parentId: zod.z.string().optional().describe("Parent document ID. Omit for top-level pages. Use a document ID for nested/child pages."),
21331
21437
  label: zod.z.string().describe("Display name / title for the document."),
21332
21438
  type: zod.z.string().optional().describe("Page type — sets how this document renders. Core types (always available): \"doc\" (rich text), \"kanban\" (columns → cards), \"table\" (columns → rows, positional), \"calendar\" (events with datetimeStart/End), \"timeline\" (epics → tasks with dateStart/End + taskProgress), \"checklist\" (tasks with checked/priority, unlimited nesting), \"outline\" (nested items, unlimited depth), \"gallery\" (visual grid with covers/ratings), \"map\" (markers/lines with geoLat/geoLng), \"graph\" (force-directed knowledge graph), \"dashboard\" (positioned widgets with deskX/deskY/deskMode), \"slides\" (slides → sub-slides with transitions), \"chart\" (bar/stacked bar/line/donut/treemap from data points or aggregation), \"sheets\" (spreadsheet with formulas and formatting), \"overview\" (space home — activity and stats), \"call\" (video call room, no children). Plugin types (require plugin enabled on the server): \"spatial\" (3D scene with spShape/spX/spY/spZ + universal color, plugin: spatial), \"media\" (audio/video player with playlists, plugin: media), \"coder\" (collaborative multi-file coding env with fileType/entry, plugin: coder). Alias: \"desktop\" → \"dashboard\". Omit to inherit parent view. Only set on the parent page, NEVER on child items."),
21333
- meta: zod.z.record(zod.z.unknown()).optional().describe("Initial metadata (PageMeta fields: color as hex string, icon as Lucide kebab-case name like \"star\"/\"code-2\"/\"users\" — never emoji, dateStart, dateEnd, priority 0-4, tags array, etc). Omit icon entirely to use page type default.")
21439
+ meta: zod.z.record(zod.z.string(), zod.z.unknown()).optional().describe("Initial metadata (PageMeta fields: color as hex string, icon as Lucide kebab-case name like \"star\"/\"code-2\"/\"users\" — never emoji, dateStart, dateEnd, priority 0-4, tags array, etc). Omit icon entirely to use page type default.")
21334
21440
  }, async ({ parentId, label, type, meta }) => {
21335
21441
  server.setAutoStatus("creating");
21336
21442
  server.setActiveToolCall({
@@ -21339,13 +21445,10 @@ function registerTreeTools(mcp, server) {
21339
21445
  });
21340
21446
  const treeMap = server.getTreeMap();
21341
21447
  const rootDoc = server.rootDocument;
21342
- if (!treeMap || !rootDoc) {
21343
- server.setActiveToolCall(null);
21344
- return { content: [{
21345
- type: "text",
21346
- text: "Not connected"
21347
- }] };
21348
- }
21448
+ if (!treeMap || !rootDoc) return { content: [{
21449
+ type: "text",
21450
+ text: "Not connected"
21451
+ }] };
21349
21452
  const id = crypto.randomUUID();
21350
21453
  const normalizedParent = normalizeRootId(parentId, server);
21351
21454
  const now = Date.now();
@@ -21361,7 +21464,6 @@ function registerTreeTools(mcp, server) {
21361
21464
  });
21362
21465
  });
21363
21466
  server.setFocusedDoc(id);
21364
- server.setActiveToolCall(null);
21365
21467
  return { content: [{
21366
21468
  type: "text",
21367
21469
  text: JSON.stringify({
@@ -21382,28 +21484,21 @@ function registerTreeTools(mcp, server) {
21382
21484
  target: id
21383
21485
  });
21384
21486
  const treeMap = server.getTreeMap();
21385
- if (!treeMap) {
21386
- server.setActiveToolCall(null);
21387
- return { content: [{
21388
- type: "text",
21389
- text: "Not connected"
21390
- }] };
21391
- }
21487
+ if (!treeMap) return { content: [{
21488
+ type: "text",
21489
+ text: "Not connected"
21490
+ }] };
21392
21491
  const raw = treeMap.get(id);
21393
- if (!raw) {
21394
- server.setActiveToolCall(null);
21395
- return { content: [{
21396
- type: "text",
21397
- text: `Document ${id} not found`
21398
- }] };
21399
- }
21492
+ if (!raw) return { content: [{
21493
+ type: "text",
21494
+ text: `Document ${id} not found`
21495
+ }] };
21400
21496
  const entry = toPlain(raw);
21401
21497
  treeMap.set(id, {
21402
21498
  ...entry,
21403
21499
  label,
21404
21500
  updatedAt: Date.now()
21405
21501
  });
21406
- server.setActiveToolCall(null);
21407
21502
  return { content: [{
21408
21503
  type: "text",
21409
21504
  text: `Renamed to "${label}"`
@@ -21420,21 +21515,15 @@ function registerTreeTools(mcp, server) {
21420
21515
  target: id
21421
21516
  });
21422
21517
  const treeMap = server.getTreeMap();
21423
- if (!treeMap) {
21424
- server.setActiveToolCall(null);
21425
- return { content: [{
21426
- type: "text",
21427
- text: "Not connected"
21428
- }] };
21429
- }
21518
+ if (!treeMap) return { content: [{
21519
+ type: "text",
21520
+ text: "Not connected"
21521
+ }] };
21430
21522
  const raw = treeMap.get(id);
21431
- if (!raw) {
21432
- server.setActiveToolCall(null);
21433
- return { content: [{
21434
- type: "text",
21435
- text: `Document ${id} not found`
21436
- }] };
21437
- }
21523
+ if (!raw) return { content: [{
21524
+ type: "text",
21525
+ text: `Document ${id} not found`
21526
+ }] };
21438
21527
  const entry = toPlain(raw);
21439
21528
  treeMap.set(id, {
21440
21529
  ...entry,
@@ -21442,7 +21531,6 @@ function registerTreeTools(mcp, server) {
21442
21531
  order: order ?? Date.now(),
21443
21532
  updatedAt: Date.now()
21444
21533
  });
21445
- server.setActiveToolCall(null);
21446
21534
  return { content: [{
21447
21535
  type: "text",
21448
21536
  text: `Moved ${id} to parent ${newParentId}`
@@ -21457,13 +21545,10 @@ function registerTreeTools(mcp, server) {
21457
21545
  const treeMap = server.getTreeMap();
21458
21546
  const trashMap = server.getTrashMap();
21459
21547
  const rootDoc = server.rootDocument;
21460
- if (!treeMap || !trashMap || !rootDoc) {
21461
- server.setActiveToolCall(null);
21462
- return { content: [{
21463
- type: "text",
21464
- text: "Not connected"
21465
- }] };
21466
- }
21548
+ if (!treeMap || !trashMap || !rootDoc) return { content: [{
21549
+ type: "text",
21550
+ text: "Not connected"
21551
+ }] };
21467
21552
  const toDelete = [id, ...descendantsOf(readEntries$1(treeMap), id).map((e) => e.id)];
21468
21553
  const now = Date.now();
21469
21554
  rootDoc.transact(() => {
@@ -21482,7 +21567,6 @@ function registerTreeTools(mcp, server) {
21482
21567
  treeMap.delete(nid);
21483
21568
  }
21484
21569
  });
21485
- server.setActiveToolCall(null);
21486
21570
  return { content: [{
21487
21571
  type: "text",
21488
21572
  text: `Deleted ${toDelete.length} document(s)`
@@ -21498,28 +21582,21 @@ function registerTreeTools(mcp, server) {
21498
21582
  target: id
21499
21583
  });
21500
21584
  const treeMap = server.getTreeMap();
21501
- if (!treeMap) {
21502
- server.setActiveToolCall(null);
21503
- return { content: [{
21504
- type: "text",
21505
- text: "Not connected"
21506
- }] };
21507
- }
21585
+ if (!treeMap) return { content: [{
21586
+ type: "text",
21587
+ text: "Not connected"
21588
+ }] };
21508
21589
  const raw = treeMap.get(id);
21509
- if (!raw) {
21510
- server.setActiveToolCall(null);
21511
- return { content: [{
21512
- type: "text",
21513
- text: `Document ${id} not found`
21514
- }] };
21515
- }
21590
+ if (!raw) return { content: [{
21591
+ type: "text",
21592
+ text: `Document ${id} not found`
21593
+ }] };
21516
21594
  const entry = toPlain(raw);
21517
21595
  treeMap.set(id, {
21518
21596
  ...entry,
21519
21597
  type,
21520
21598
  updatedAt: Date.now()
21521
21599
  });
21522
- server.setActiveToolCall(null);
21523
21600
  return { content: [{
21524
21601
  type: "text",
21525
21602
  text: `Changed type to "${type}"`
@@ -22775,7 +22852,6 @@ function registerContentTools(mcp, server) {
22775
22852
  });
22776
22853
  children.sort((a, b) => (treeMap.get(a.id)?.order ?? 0) - (treeMap.get(b.id)?.order ?? 0));
22777
22854
  }
22778
- server.setActiveToolCall(null);
22779
22855
  const result = {
22780
22856
  label,
22781
22857
  type,
@@ -22788,7 +22864,6 @@ function registerContentTools(mcp, server) {
22788
22864
  text: JSON.stringify(result, null, 2)
22789
22865
  }] };
22790
22866
  } catch (error) {
22791
- server.setActiveToolCall(null);
22792
22867
  return {
22793
22868
  content: [{
22794
22869
  type: "text",
@@ -22838,13 +22913,11 @@ function registerContentTools(mcp, server) {
22838
22913
  populateYDocFromMarkdown(fragment, body || markdown, title || "Untitled");
22839
22914
  server.setFocusedDoc(docId);
22840
22915
  server.setDocCursor(docId, fragment.length);
22841
- server.setActiveToolCall(null);
22842
22916
  return { content: [{
22843
22917
  type: "text",
22844
22918
  text: `Document ${docId} updated (${writeMode} mode)`
22845
22919
  }] };
22846
22920
  } catch (error) {
22847
- server.setActiveToolCall(null);
22848
22921
  return {
22849
22922
  content: [{
22850
22923
  type: "text",
@@ -22866,22 +22939,15 @@ function registerMetaTools(mcp, server) {
22866
22939
  target: docId
22867
22940
  });
22868
22941
  const treeMap = server.getTreeMap();
22869
- if (!treeMap) {
22870
- server.setActiveToolCall(null);
22871
- return { content: [{
22872
- type: "text",
22873
- text: "Not connected"
22874
- }] };
22875
- }
22942
+ if (!treeMap) return { content: [{
22943
+ type: "text",
22944
+ text: "Not connected"
22945
+ }] };
22876
22946
  const entry = treeMap.get(docId);
22877
- if (!entry) {
22878
- server.setActiveToolCall(null);
22879
- return { content: [{
22880
- type: "text",
22881
- text: `Document ${docId} not found`
22882
- }] };
22883
- }
22884
- server.setActiveToolCall(null);
22947
+ if (!entry) return { content: [{
22948
+ type: "text",
22949
+ text: `Document ${docId} not found`
22950
+ }] };
22885
22951
  return { content: [{
22886
22952
  type: "text",
22887
22953
  text: JSON.stringify({
@@ -22894,7 +22960,7 @@ function registerMetaTools(mcp, server) {
22894
22960
  });
22895
22961
  mcp.tool("update_metadata", "Update metadata fields on a document. Merges the provided fields into existing metadata.", {
22896
22962
  docId: zod.z.string().describe("Document ID."),
22897
- meta: zod.z.record(zod.z.unknown()).describe("Metadata fields to update (merged with existing). Universal keys: color (hex), icon (Lucide kebab-case — NEVER emoji), dateStart/dateEnd, datetimeStart/datetimeEnd, allDay, timeStart/timeEnd, tags (string[]), checked (bool), priority (0=none,1=low,2=med,3=high,4=urgent), status, rating (0-5), url, email, phone, number, unit, subtitle, note, taskProgress (0-100), members ({id,label}[]), coverUploadId, coverDocId, dateTaken. Geo/Map (children): geoType (\"marker\"|\"line\"|\"measure\"), geoLat, geoLng, geoDescription. Spatial 3D (children, plugin: spatial): spShape (\"box\"|\"sphere\"|\"cylinder\"|\"cone\"|\"plane\"|\"torus\"|\"glb\"), spX/spY/spZ, spRX/spRY/spRZ (deg), spSX/spSY/spSZ (scale), spOpacity (0-100), spModelUploadId, spModelDocId — spatial uses the universal `color` key, NOT spColor. Dashboard (children): deskX, deskY, deskZ, deskMode (\"icon\"|\"widget-sm\"|\"widget-lg\"). Mindmap-layout (children): mmX, mmY. Graph-layout (children): graphX, graphY, graphPinned. Slides (children): slidesTransition (\"none\"|\"fade\"|\"slide\"). Coder (children, plugin: coder): fileType (\"vue\"|\"ts\"|\"js\"|\"css\"|\"json\"|\"folder\"), entry (bool). Cell formatting (sheets cells): bold, italic, textColor, bgColor, align (\"left\"|\"center\"|\"right\"), formula. Renderer config (on the PAGE doc itself, not children): kanbanColumnWidth (\"narrow\"|\"default\"|\"wide\"), galleryColumns (1-6), galleryAspect (\"square\"|\"4:3\"|\"3:2\"|\"16:9\"|\"free\"), galleryCardStyle (\"default\"|\"compact\"|\"detailed\"), galleryShowLabels, gallerySortBy (\"manual\"|\"date\"|\"name\"|\"rating\"), calendarView (\"month\"|\"week\"|\"day\"), calendarWeekStart (\"sun\"|\"mon\"), calendarShowWeekNumbers, tableMode (\"hierarchy\"|\"flat\"), tableSortKey, tableSortDir (\"asc\"|\"desc\"), timelineZoom (\"week\"|\"month\"|\"quarter\"), timelinePixelsPerDay, timelineCenterDate (ISO date), checklistFilter (\"all\"|\"active\"|\"completed\"), checklistSort (\"manual\"|\"priority\"|\"due\"), mapShowLabels, graphSpacing (\"compact\"|\"default\"|\"spacious\"), graphShowLabels, graphEdgeThickness (\"thin\"|\"normal\"|\"thick\"), showRefEdges, mmSpacing, spatialGridVisible, slidesTheme (\"dark\"|\"light\"), chartType (\"bar\"|\"stacked bar\"|\"line\"|\"donut\"|\"treemap\"), chartMetric (\"value\"|\"type\"|\"tag\"|\"status\"|\"priority\"|\"activity\"|\"completion\"), chartColorScheme (\"default\"|\"warm\"|\"cool\"|\"mono\"), chartLimit (3-30), chartShowLegend, chartShowValues, sheetsDefaultColWidth (40-500), sheetsDefaultRowHeight (20-100), sheetsShowGridlines, sheetsFreezeRows, sheetsFreezeCols, mediaRepeat (\"off\"|\"all\"|\"one\"), mediaShuffle. Set a key to null to clear it.")
22963
+ meta: zod.z.record(zod.z.string(), zod.z.unknown()).describe("Metadata fields to update (merged with existing). Universal keys: color (hex), icon (Lucide kebab-case — NEVER emoji), dateStart/dateEnd, datetimeStart/datetimeEnd, allDay, timeStart/timeEnd, tags (string[]), checked (bool), priority (0=none,1=low,2=med,3=high,4=urgent), status, rating (0-5), url, email, phone, number, unit, subtitle, note, taskProgress (0-100), members ({id,label}[]), coverUploadId, coverDocId, dateTaken. Geo/Map (children): geoType (\"marker\"|\"line\"|\"measure\"), geoLat, geoLng, geoDescription. Spatial 3D (children, plugin: spatial): spShape (\"box\"|\"sphere\"|\"cylinder\"|\"cone\"|\"plane\"|\"torus\"|\"glb\"), spX/spY/spZ, spRX/spRY/spRZ (deg), spSX/spSY/spSZ (scale), spOpacity (0-100), spModelUploadId, spModelDocId — spatial uses the universal `color` key, NOT spColor. Dashboard (children): deskX, deskY, deskZ, deskMode (\"icon\"|\"widget-sm\"|\"widget-lg\"). Mindmap-layout (children): mmX, mmY. Graph-layout (children): graphX, graphY, graphPinned. Slides (children): slidesTransition (\"none\"|\"fade\"|\"slide\"). Coder (children, plugin: coder): fileType (\"vue\"|\"ts\"|\"js\"|\"css\"|\"json\"|\"folder\"), entry (bool). Cell formatting (sheets cells): bold, italic, textColor, bgColor, align (\"left\"|\"center\"|\"right\"), formula. Renderer config (on the PAGE doc itself, not children): kanbanColumnWidth (\"narrow\"|\"default\"|\"wide\"), galleryColumns (1-6), galleryAspect (\"square\"|\"4:3\"|\"3:2\"|\"16:9\"|\"free\"), galleryCardStyle (\"default\"|\"compact\"|\"detailed\"), galleryShowLabels, gallerySortBy (\"manual\"|\"date\"|\"name\"|\"rating\"), calendarView (\"month\"|\"week\"|\"day\"), calendarWeekStart (\"sun\"|\"mon\"), calendarShowWeekNumbers, tableMode (\"hierarchy\"|\"flat\"), tableSortKey, tableSortDir (\"asc\"|\"desc\"), timelineZoom (\"week\"|\"month\"|\"quarter\"), timelinePixelsPerDay, timelineCenterDate (ISO date), checklistFilter (\"all\"|\"active\"|\"completed\"), checklistSort (\"manual\"|\"priority\"|\"due\"), mapShowLabels, graphSpacing (\"compact\"|\"default\"|\"spacious\"), graphShowLabels, graphEdgeThickness (\"thin\"|\"normal\"|\"thick\"), showRefEdges, mmSpacing, spatialGridVisible, slidesTheme (\"dark\"|\"light\"), chartType (\"bar\"|\"stacked bar\"|\"line\"|\"donut\"|\"treemap\"), chartMetric (\"value\"|\"type\"|\"tag\"|\"status\"|\"priority\"|\"activity\"|\"completion\"), chartColorScheme (\"default\"|\"warm\"|\"cool\"|\"mono\"), chartLimit (3-30), chartShowLegend, chartShowValues, sheetsDefaultColWidth (40-500), sheetsDefaultRowHeight (20-100), sheetsShowGridlines, sheetsFreezeRows, sheetsFreezeCols, mediaRepeat (\"off\"|\"all\"|\"one\"), mediaShuffle. Set a key to null to clear it.")
22898
22964
  }, async ({ docId, meta }) => {
22899
22965
  server.setAutoStatus("writing", docId);
22900
22966
  server.setActiveToolCall({
@@ -22902,21 +22968,15 @@ function registerMetaTools(mcp, server) {
22902
22968
  target: docId
22903
22969
  });
22904
22970
  const treeMap = server.getTreeMap();
22905
- if (!treeMap) {
22906
- server.setActiveToolCall(null);
22907
- return { content: [{
22908
- type: "text",
22909
- text: "Not connected"
22910
- }] };
22911
- }
22971
+ if (!treeMap) return { content: [{
22972
+ type: "text",
22973
+ text: "Not connected"
22974
+ }] };
22912
22975
  const entry = treeMap.get(docId);
22913
- if (!entry) {
22914
- server.setActiveToolCall(null);
22915
- return { content: [{
22916
- type: "text",
22917
- text: `Document ${docId} not found`
22918
- }] };
22919
- }
22976
+ if (!entry) return { content: [{
22977
+ type: "text",
22978
+ text: `Document ${docId} not found`
22979
+ }] };
22920
22980
  treeMap.set(docId, {
22921
22981
  ...entry,
22922
22982
  meta: {
@@ -22925,7 +22985,6 @@ function registerMetaTools(mcp, server) {
22925
22985
  },
22926
22986
  updatedAt: Date.now()
22927
22987
  });
22928
- server.setActiveToolCall(null);
22929
22988
  return { content: [{
22930
22989
  type: "text",
22931
22990
  text: `Metadata updated for ${docId}`
@@ -22940,6 +22999,11 @@ function registerMetaTools(mcp, server) {
22940
22999
  */
22941
23000
  function registerFileTools(mcp, server) {
22942
23001
  mcp.tool("list_uploads", "List file attachments for a document.", { docId: zod.z.string().describe("Document ID.") }, async ({ docId }) => {
23002
+ server.setAutoStatus("reading", docId);
23003
+ server.setActiveToolCall({
23004
+ name: "list_uploads",
23005
+ target: docId
23006
+ });
22943
23007
  try {
22944
23008
  const uploads = await server.client.listUploads(docId);
22945
23009
  return { content: [{
@@ -22961,6 +23025,11 @@ function registerFileTools(mcp, server) {
22961
23025
  filePath: zod.z.string().describe("Absolute path to the local file to upload."),
22962
23026
  filename: zod.z.string().optional().describe("Override filename (defaults to basename of filePath).")
22963
23027
  }, async ({ docId, filePath, filename }) => {
23028
+ server.setAutoStatus("uploading", docId);
23029
+ server.setActiveToolCall({
23030
+ name: "upload_file",
23031
+ target: node_path.basename(filePath)
23032
+ });
22964
23033
  try {
22965
23034
  const resolvedPath = node_path.resolve(filePath);
22966
23035
  const data = node_fs.readFileSync(resolvedPath);
@@ -22986,6 +23055,11 @@ function registerFileTools(mcp, server) {
22986
23055
  uploadId: zod.z.string().describe("Upload ID to download."),
22987
23056
  saveTo: zod.z.string().describe("Absolute local file path to save the download.")
22988
23057
  }, async ({ docId, uploadId, saveTo }) => {
23058
+ server.setAutoStatus("reading", docId);
23059
+ server.setActiveToolCall({
23060
+ name: "download_file",
23061
+ target: node_path.basename(saveTo)
23062
+ });
22989
23063
  try {
22990
23064
  const blob = await server.client.getUpload(docId, uploadId);
22991
23065
  const buffer = Buffer.from(await blob.arrayBuffer());
@@ -23009,6 +23083,11 @@ function registerFileTools(mcp, server) {
23009
23083
  docId: zod.z.string().describe("Document ID."),
23010
23084
  uploadId: zod.z.string().describe("Upload ID to delete.")
23011
23085
  }, async ({ docId, uploadId }) => {
23086
+ server.setAutoStatus("writing", docId);
23087
+ server.setActiveToolCall({
23088
+ name: "delete_file",
23089
+ target: uploadId
23090
+ });
23012
23091
  try {
23013
23092
  await server.client.deleteUpload(docId, uploadId);
23014
23093
  return { content: [{
@@ -23050,6 +23129,10 @@ function registerAwarenessTools(mcp, server) {
23050
23129
  docId: zod.z.string().describe("Document ID to set awareness on."),
23051
23130
  fields: zod.z.record(zod.z.string(), zod.z.unknown()).describe("Key-value pairs to set on the child document's awareness state. Use namespaced keys like \"kanban:hovering\", \"table:editing\", \"slides:viewing\", \"outline:editing\", \"calendar:focused\", \"gallery:focused\", \"timeline:focused\", \"graph:focused\", \"map:focused\", \"doc:scroll\". Set a key to null to clear it.")
23052
23131
  }, async ({ docId, fields }) => {
23132
+ server.setActiveToolCall({
23133
+ name: "set_doc_awareness",
23134
+ target: docId
23135
+ });
23053
23136
  try {
23054
23137
  const provider = await server.getChildProvider(docId);
23055
23138
  for (const [key, value] of Object.entries(fields)) provider.awareness.setLocalStateField(key, value ?? null);
@@ -23068,6 +23151,7 @@ function registerAwarenessTools(mcp, server) {
23068
23151
  }
23069
23152
  });
23070
23153
  mcp.tool("poll_inbox", "Check the \"AI Inbox\" document for pending instructions from humans. Returns the inbox content and any pending task sub-documents. Create the inbox as a doc called \"AI Inbox\" under the hub doc if it does not exist yet. Note: channel-based watching via watch_chat is preferred for real-time use.", {}, async () => {
23154
+ server.setActiveToolCall({ name: "poll_inbox" });
23071
23155
  try {
23072
23156
  const treeMap = server.getTreeMap();
23073
23157
  const rootDocId = server.rootDocId;
@@ -23114,6 +23198,10 @@ function registerAwarenessTools(mcp, server) {
23114
23198
  }
23115
23199
  });
23116
23200
  mcp.tool("list_connected_users", "List all connected users and their awareness state. Shows who is online and what they are doing.", { docId: zod.z.string().optional().describe("If provided, list users connected to this specific document. Otherwise lists users from root awareness.") }, async ({ docId }) => {
23201
+ server.setActiveToolCall({
23202
+ name: "list_connected_users",
23203
+ target: docId
23204
+ });
23117
23205
  try {
23118
23206
  let awareness;
23119
23207
  if (docId) awareness = (await server.getChildProvider(docId)).awareness;
@@ -23158,16 +23246,13 @@ function registerChannelTools(mcp, server) {
23158
23246
  });
23159
23247
  const treeMap = server.getTreeMap();
23160
23248
  const rootDoc = server.rootDocument;
23161
- if (!treeMap || !rootDoc) {
23162
- server.setActiveToolCall(null);
23163
- return {
23164
- content: [{
23165
- type: "text",
23166
- text: "Not connected"
23167
- }],
23168
- isError: true
23169
- };
23170
- }
23249
+ if (!treeMap || !rootDoc) return {
23250
+ content: [{
23251
+ type: "text",
23252
+ text: "Not connected"
23253
+ }],
23254
+ isError: true
23255
+ };
23171
23256
  const label = `AI Reply — ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)}: ${text.slice(0, 40).replace(/\n/g, " ")}`;
23172
23257
  const replyId = crypto.randomUUID();
23173
23258
  const now = Date.now();
@@ -23183,7 +23268,6 @@ function registerChannelTools(mcp, server) {
23183
23268
  });
23184
23269
  populateYDocFromMarkdown((await server.getChildProvider(replyId)).document, text);
23185
23270
  if (task_id) server.clearAiTask(task_id);
23186
- server.setActiveToolCall(null);
23187
23271
  return { content: [{
23188
23272
  type: "text",
23189
23273
  text: JSON.stringify({
@@ -23192,7 +23276,6 @@ function registerChannelTools(mcp, server) {
23192
23276
  })
23193
23277
  }] };
23194
23278
  } catch (error) {
23195
- server.setActiveToolCall(null);
23196
23279
  return {
23197
23280
  content: [{
23198
23281
  type: "text",
@@ -23215,14 +23298,15 @@ function registerChannelTools(mcp, server) {
23215
23298
  }],
23216
23299
  isError: true
23217
23300
  };
23301
+ const normalized = text.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\n");
23302
+ server.setAutoStatus(null);
23303
+ server.sendTypingIndicator(channel);
23218
23304
  rootProvider.sendStateless(JSON.stringify({
23219
23305
  type: "chat:send",
23220
23306
  channel,
23221
- content: text,
23307
+ content: normalized,
23222
23308
  sender_name: server.agentName
23223
23309
  }));
23224
- server.setAutoStatus(null);
23225
- server.setActiveToolCall(null);
23226
23310
  return { content: [{
23227
23311
  type: "text",
23228
23312
  text: `Sent to ${channel}`
@@ -23377,16 +23461,13 @@ function registerSvgTools(mcp, server) {
23377
23461
  target: docId
23378
23462
  });
23379
23463
  const cleanSvg = sanitizeSvg(svg);
23380
- if (!cleanSvg) {
23381
- server.setActiveToolCall(null);
23382
- return {
23383
- content: [{
23384
- type: "text",
23385
- text: "Error: SVG markup was empty or entirely stripped by sanitizer."
23386
- }],
23387
- isError: true
23388
- };
23389
- }
23464
+ if (!cleanSvg) return {
23465
+ content: [{
23466
+ type: "text",
23467
+ text: "Error: SVG markup was empty or entirely stripped by sanitizer."
23468
+ }],
23469
+ isError: true
23470
+ };
23390
23471
  const doc = (await server.getChildProvider(docId)).document;
23391
23472
  const fragment = doc.getXmlFragment("default");
23392
23473
  doc.transact(() => {
@@ -23397,13 +23478,11 @@ function registerSvgTools(mcp, server) {
23397
23478
  fragment.insert(insertPos, [el]);
23398
23479
  });
23399
23480
  server.setFocusedDoc(docId);
23400
- server.setActiveToolCall(null);
23401
23481
  return { content: [{
23402
23482
  type: "text",
23403
23483
  text: `SVG inserted into document ${docId}${title ? ` ("${title}")` : ""}`
23404
23484
  }] };
23405
23485
  } catch (error) {
23406
- server.setActiveToolCall(null);
23407
23486
  return {
23408
23487
  content: [{
23409
23488
  type: "text",
@@ -24207,6 +24286,9 @@ var HookBridge = class {
24207
24286
  }
24208
24287
  routeEvent(payload) {
24209
24288
  switch (payload.hook_event_name) {
24289
+ case "UserPromptSubmit":
24290
+ this.onUserPromptSubmit();
24291
+ break;
24210
24292
  case "PreToolUse":
24211
24293
  this.onPreToolUse(payload);
24212
24294
  break;
@@ -24217,13 +24299,18 @@ var HookBridge = class {
24217
24299
  this.onSubagentStart(payload);
24218
24300
  break;
24219
24301
  case "SubagentStop":
24220
- this.onSubagentStop(payload);
24302
+ this.onSubagentStop();
24221
24303
  break;
24222
24304
  case "Stop":
24223
24305
  this.onStop();
24224
24306
  break;
24225
24307
  }
24226
24308
  }
24309
+ /** New user turn — reset any lingering status/tool state from the previous turn. */
24310
+ onUserPromptSubmit() {
24311
+ this.server.setAutoStatus(null);
24312
+ this.server.setActiveToolCall(null);
24313
+ }
24227
24314
  onPreToolUse(payload) {
24228
24315
  const toolName = payload.tool_name ?? "";
24229
24316
  if (toolName.startsWith("mcp__abracadabra__")) return;
@@ -24235,7 +24322,6 @@ var HookBridge = class {
24235
24322
  }
24236
24323
  onPostToolUse(payload) {
24237
24324
  if ((payload.tool_name ?? "").startsWith("mcp__abracadabra__")) return;
24238
- this.server.setAutoStatus("thinking");
24239
24325
  }
24240
24326
  onSubagentStart(payload) {
24241
24327
  const agentType = payload.agent_type ?? "agent";
@@ -24245,9 +24331,7 @@ var HookBridge = class {
24245
24331
  });
24246
24332
  this.server.setAutoStatus("thinking");
24247
24333
  }
24248
- onSubagentStop(_payload) {
24249
- this.server.setAutoStatus("thinking");
24250
- }
24334
+ onSubagentStop() {}
24251
24335
  onStop() {
24252
24336
  this.server.setAutoStatus(null);
24253
24337
  this.server.setActiveToolCall(null);
@@ -24260,11 +24344,19 @@ var HookBridge = class {
24260
24344
  * Abracadabra MCP Server — entry point.
24261
24345
  *
24262
24346
  * Environment variables:
24263
- * ABRA_URL (required) — Server URL (e.g. http://localhost:1234)
24347
+ * ABRA_URL (required) — Server URL (e.g. http://localhost:1234)
24264
24348
  * ABRA_AGENT_NAME — Display name (default: "AI Assistant")
24265
24349
  * ABRA_AGENT_COLOR — HSL color for presence (default: "hsl(270, 80%, 60%)")
24266
24350
  * ABRA_INVITE_CODE — Invite code for first-run registration (grants role)
24267
24351
  * ABRA_KEY_FILE — Path to Ed25519 key file (default: ~/.abracadabra/agent.key)
24352
+ * ABRA_AGENT_TRIGGER_MODE — When to respond in group chats:
24353
+ * all → every message (legacy)
24354
+ * mention → only when @<alias> is used
24355
+ * task → only ai:task awareness events
24356
+ * mention+task → mention OR ai:task (default)
24357
+ * DMs always trigger regardless of mode.
24358
+ * ABRA_AGENT_MENTION_ALIASES — Comma-separated aliases for @mentions
24359
+ * (default: [ABRA_AGENT_NAME])
24268
24360
  */
24269
24361
  async function main() {
24270
24362
  const url = process.env.ABRA_URL;
@@ -24272,13 +24364,27 @@ async function main() {
24272
24364
  console.error("Missing required environment variable: ABRA_URL");
24273
24365
  process.exit(1);
24274
24366
  }
24367
+ const rawMode = (process.env.ABRA_AGENT_TRIGGER_MODE ?? "mention+task").trim().toLowerCase();
24368
+ const validModes = [
24369
+ "all",
24370
+ "mention",
24371
+ "task",
24372
+ "mention+task"
24373
+ ];
24374
+ const triggerMode = validModes.includes(rawMode) ? rawMode : "mention+task";
24375
+ if (rawMode && !validModes.includes(rawMode)) console.error(`[abracadabra-mcp] Invalid ABRA_AGENT_TRIGGER_MODE="${rawMode}", falling back to "mention+task"`);
24376
+ const aliasEnv = process.env.ABRA_AGENT_MENTION_ALIASES;
24377
+ const mentionAliases = aliasEnv ? aliasEnv.split(",").map((a) => a.trim()).filter((a) => a.length > 0) : void 0;
24275
24378
  const server = new AbracadabraMCPServer({
24276
24379
  url,
24277
24380
  agentName: process.env.ABRA_AGENT_NAME,
24278
24381
  agentColor: process.env.ABRA_AGENT_COLOR,
24279
24382
  inviteCode: process.env.ABRA_INVITE_CODE,
24280
- keyFile: process.env.ABRA_KEY_FILE
24383
+ keyFile: process.env.ABRA_KEY_FILE,
24384
+ triggerMode,
24385
+ mentionAliases
24281
24386
  });
24387
+ console.error(`[abracadabra-mcp] Trigger mode: ${triggerMode}; aliases: ${server.mentionAliases.join(", ")}`);
24282
24388
  const mcp = new McpServer({
24283
24389
  name: "abracadabra",
24284
24390
  version: "1.0.0"