@abraca/mcp 1.0.11 → 1.0.15

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.
@@ -19446,6 +19446,9 @@ var AbracadabraMCPServer = class {
19446
19446
  this._serverRef = null;
19447
19447
  this._handledTaskIds = /* @__PURE__ */ new Set();
19448
19448
  this._userId = null;
19449
+ this._statusClearTimer = null;
19450
+ this._typingInterval = null;
19451
+ this._lastChatChannel = null;
19449
19452
  this.config = config;
19450
19453
  this.client = new _abraca_dabra.AbracadabraClient({
19451
19454
  url: config.url,
@@ -19540,7 +19543,8 @@ var AbracadabraMCPServer = class {
19540
19543
  provider.awareness.setLocalStateField("user", {
19541
19544
  name: this.agentName,
19542
19545
  color: this.agentColor,
19543
- publicKey: this._userId
19546
+ publicKey: this._userId,
19547
+ isAgent: true
19544
19548
  });
19545
19549
  const conn = {
19546
19550
  doc,
@@ -19593,7 +19597,8 @@ var AbracadabraMCPServer = class {
19593
19597
  childProvider.awareness.setLocalStateField("user", {
19594
19598
  name: this.agentName,
19595
19599
  color: this.agentColor,
19596
- publicKey: this._userId
19600
+ publicKey: this._userId,
19601
+ isAgent: true
19597
19602
  });
19598
19603
  this.childCache.set(docId, {
19599
19604
  provider: childProvider,
@@ -19660,6 +19665,7 @@ var AbracadabraMCPServer = class {
19660
19665
  const user = state["user"];
19661
19666
  const senderName = user && typeof user === "object" && typeof user.name === "string" ? user.name : "Unknown";
19662
19667
  console.error(`[abracadabra-mcp] Handling ai:task id=${id} from ${senderName}: ${text.slice(0, 80)}`);
19668
+ this.setAutoStatus("thinking");
19663
19669
  this._dispatchAiTask({
19664
19670
  id,
19665
19671
  text,
@@ -19724,6 +19730,18 @@ var AbracadabraMCPServer = class {
19724
19730
  const parts = channel.split(":");
19725
19731
  if (parts.length === 3 && parts[1] !== this._userId && parts[2] !== this._userId) return;
19726
19732
  }
19733
+ if (channel) {
19734
+ const rootProvider = this._activeConnection?.provider;
19735
+ if (rootProvider) rootProvider.sendStateless(JSON.stringify({
19736
+ type: "chat:mark_read",
19737
+ channel,
19738
+ timestamp: Math.floor(Date.now() / 1e3)
19739
+ }));
19740
+ this._lastChatChannel = channel;
19741
+ this.sendTypingIndicator(channel);
19742
+ this._startTypingInterval(channel);
19743
+ }
19744
+ this.setAutoStatus("thinking");
19727
19745
  await this._serverRef.notification({
19728
19746
  method: "notifications/claude/channel",
19729
19747
  params: {
@@ -19741,8 +19759,65 @@ var AbracadabraMCPServer = class {
19741
19759
  console.error(`[abracadabra-mcp] Chat message from ${data.sender_name ?? "unknown"} on ${channel}`);
19742
19760
  } catch {}
19743
19761
  }
19762
+ /**
19763
+ * Set the agent's status in root awareness with auto-clear after idle.
19764
+ */
19765
+ setAutoStatus(status, docId) {
19766
+ const provider = this._activeConnection?.provider;
19767
+ if (!provider) return;
19768
+ if (this._statusClearTimer) {
19769
+ clearTimeout(this._statusClearTimer);
19770
+ this._statusClearTimer = null;
19771
+ }
19772
+ provider.awareness.setLocalStateField("status", status);
19773
+ if (docId !== void 0) provider.awareness.setLocalStateField("docId", docId);
19774
+ if (!status) this._stopTypingInterval();
19775
+ if (status) this._statusClearTimer = setTimeout(() => {
19776
+ provider.awareness.setLocalStateField("status", null);
19777
+ provider.awareness.setLocalStateField("activeToolCall", null);
19778
+ this._stopTypingInterval();
19779
+ }, 3e4);
19780
+ }
19781
+ /** Re-send typing indicator every 2s so dashboard keeps showing it (expires at 3s). */
19782
+ _startTypingInterval(channel) {
19783
+ this._stopTypingInterval();
19784
+ this._typingInterval = setInterval(() => {
19785
+ this.sendTypingIndicator(channel);
19786
+ }, 2e3);
19787
+ }
19788
+ _stopTypingInterval() {
19789
+ if (this._typingInterval) {
19790
+ clearInterval(this._typingInterval);
19791
+ this._typingInterval = null;
19792
+ }
19793
+ this._lastChatChannel = null;
19794
+ }
19795
+ /**
19796
+ * Broadcast which tool the agent is currently executing.
19797
+ * Dashboard renders this as a ChatTool indicator.
19798
+ */
19799
+ setActiveToolCall(toolCall) {
19800
+ this._activeConnection?.provider?.awareness.setLocalStateField("activeToolCall", toolCall);
19801
+ }
19802
+ /**
19803
+ * Send a typing indicator to a chat channel.
19804
+ */
19805
+ sendTypingIndicator(channel) {
19806
+ const rootProvider = this._activeConnection?.provider;
19807
+ if (!rootProvider) return;
19808
+ rootProvider.sendStateless(JSON.stringify({
19809
+ type: "chat:typing",
19810
+ channel,
19811
+ sender_name: this.agentName
19812
+ }));
19813
+ }
19744
19814
  /** Graceful shutdown. */
19745
19815
  async destroy() {
19816
+ this._stopTypingInterval();
19817
+ if (this._statusClearTimer) {
19818
+ clearTimeout(this._statusClearTimer);
19819
+ this._statusClearTimer = null;
19820
+ }
19746
19821
  if (this.evictionTimer) {
19747
19822
  clearInterval(this.evictionTimer);
19748
19823
  this.evictionTimer = null;
@@ -19894,12 +19969,20 @@ function registerTreeTools(mcp, server) {
19894
19969
  type: zod.z.string().optional().describe("Page type: \"doc\", \"kanban\", \"calendar\", \"table\", \"outline\", \"gallery\", \"slides\", \"timeline\", \"whiteboard\", \"map\", \"dashboard\", \"mindmap\", \"graph\". Omit to inherit parent view."),
19895
19970
  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.")
19896
19971
  }, async ({ parentId, label, type, meta }) => {
19972
+ server.setAutoStatus("creating");
19973
+ server.setActiveToolCall({
19974
+ name: "create_document",
19975
+ target: label
19976
+ });
19897
19977
  const treeMap = server.getTreeMap();
19898
19978
  const rootDoc = server.rootDocument;
19899
- if (!treeMap || !rootDoc) return { content: [{
19900
- type: "text",
19901
- text: "Not connected"
19902
- }] };
19979
+ if (!treeMap || !rootDoc) {
19980
+ server.setActiveToolCall(null);
19981
+ return { content: [{
19982
+ type: "text",
19983
+ text: "Not connected"
19984
+ }] };
19985
+ }
19903
19986
  const id = crypto.randomUUID();
19904
19987
  const normalizedParent = normalizeRootId(parentId, server);
19905
19988
  const now = Date.now();
@@ -19914,6 +19997,7 @@ function registerTreeTools(mcp, server) {
19914
19997
  updatedAt: now
19915
19998
  });
19916
19999
  });
20000
+ server.setActiveToolCall(null);
19917
20001
  return { content: [{
19918
20002
  type: "text",
19919
20003
  text: JSON.stringify({
@@ -21151,6 +21235,11 @@ function yjsToMarkdown(fragment) {
21151
21235
  function registerContentTools(mcp, server) {
21152
21236
  mcp.tool("read_document", "Read a document's content as markdown, along with its immediate children. IMPORTANT: A document's full content is its body text PLUS all its children. An empty body does not mean empty content — the children ARE the content (sub-docs, kanban columns, table columns, calendar events, etc.). Always check the returned \"children\" array and read child documents too.", { docId: zod.z.string().describe("Document ID to read.") }, async ({ docId }) => {
21153
21237
  try {
21238
+ server.setAutoStatus("reading", docId);
21239
+ server.setActiveToolCall({
21240
+ name: "read_document",
21241
+ target: docId
21242
+ });
21154
21243
  const { title, markdown } = yjsToMarkdown((await server.getChildProvider(docId)).document.getXmlFragment("default"));
21155
21244
  server.setFocusedDoc(docId);
21156
21245
  server.setDocCursor(docId, 0);
@@ -21176,6 +21265,7 @@ function registerContentTools(mcp, server) {
21176
21265
  });
21177
21266
  children.sort((a, b) => (treeMap.get(a.id)?.order ?? 0) - (treeMap.get(b.id)?.order ?? 0));
21178
21267
  }
21268
+ server.setActiveToolCall(null);
21179
21269
  const result = {
21180
21270
  label,
21181
21271
  type,
@@ -21188,6 +21278,7 @@ function registerContentTools(mcp, server) {
21188
21278
  text: JSON.stringify(result, null, 2)
21189
21279
  }] };
21190
21280
  } catch (error) {
21281
+ server.setActiveToolCall(null);
21191
21282
  return {
21192
21283
  content: [{
21193
21284
  type: "text",
@@ -21203,6 +21294,11 @@ function registerContentTools(mcp, server) {
21203
21294
  mode: zod.z.enum(["replace", "append"]).optional().describe("Write mode. \"replace\" clears existing content first (default). \"append\" adds to the end.")
21204
21295
  }, async ({ docId, markdown, mode }) => {
21205
21296
  try {
21297
+ server.setAutoStatus("writing", docId);
21298
+ server.setActiveToolCall({
21299
+ name: "write_document",
21300
+ target: docId
21301
+ });
21206
21302
  const writeMode = mode ?? "replace";
21207
21303
  const doc = (await server.getChildProvider(docId)).document;
21208
21304
  const fragment = doc.getXmlFragment("default");
@@ -21232,11 +21328,13 @@ function registerContentTools(mcp, server) {
21232
21328
  populateYDocFromMarkdown(fragment, body || markdown, title || "Untitled");
21233
21329
  server.setFocusedDoc(docId);
21234
21330
  server.setDocCursor(docId, fragment.length);
21331
+ server.setActiveToolCall(null);
21235
21332
  return { content: [{
21236
21333
  type: "text",
21237
21334
  text: `Document ${docId} updated (${writeMode} mode)`
21238
21335
  }] };
21239
21336
  } catch (error) {
21337
+ server.setActiveToolCall(null);
21240
21338
  return {
21241
21339
  content: [{
21242
21340
  type: "text",
@@ -21519,15 +21617,23 @@ function registerChannelTools(mcp, server) {
21519
21617
  task_id: zod.z.string().optional().describe("If replying to an ai:task, the task ID to clear from awareness.")
21520
21618
  }, async ({ doc_id, text, task_id }) => {
21521
21619
  try {
21620
+ server.setAutoStatus("writing", doc_id);
21621
+ server.setActiveToolCall({
21622
+ name: "reply",
21623
+ target: doc_id
21624
+ });
21522
21625
  const treeMap = server.getTreeMap();
21523
21626
  const rootDoc = server.rootDocument;
21524
- if (!treeMap || !rootDoc) return {
21525
- content: [{
21526
- type: "text",
21527
- text: "Not connected"
21528
- }],
21529
- isError: true
21530
- };
21627
+ if (!treeMap || !rootDoc) {
21628
+ server.setActiveToolCall(null);
21629
+ return {
21630
+ content: [{
21631
+ type: "text",
21632
+ text: "Not connected"
21633
+ }],
21634
+ isError: true
21635
+ };
21636
+ }
21531
21637
  const label = `AI Reply — ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)}: ${text.slice(0, 40).replace(/\n/g, " ")}`;
21532
21638
  const replyId = crypto.randomUUID();
21533
21639
  const now = Date.now();
@@ -21543,6 +21649,7 @@ function registerChannelTools(mcp, server) {
21543
21649
  });
21544
21650
  populateYDocFromMarkdown((await server.getChildProvider(replyId)).document, text);
21545
21651
  if (task_id) server.clearAiTask(task_id);
21652
+ server.setActiveToolCall(null);
21546
21653
  return { content: [{
21547
21654
  type: "text",
21548
21655
  text: JSON.stringify({
@@ -21551,6 +21658,7 @@ function registerChannelTools(mcp, server) {
21551
21658
  })
21552
21659
  }] };
21553
21660
  } catch (error) {
21661
+ server.setActiveToolCall(null);
21554
21662
  return {
21555
21663
  content: [{
21556
21664
  type: "text",
@@ -21579,6 +21687,8 @@ function registerChannelTools(mcp, server) {
21579
21687
  content: text,
21580
21688
  sender_name: server.agentName
21581
21689
  }));
21690
+ server.setAutoStatus(null);
21691
+ server.setActiveToolCall(null);
21582
21692
  return { content: [{
21583
21693
  type: "text",
21584
21694
  text: `Sent to ${channel}`