@abraca/mcp 1.0.12 → 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.
@@ -19443,6 +19443,9 @@ var AbracadabraMCPServer = class {
19443
19443
  this._serverRef = null;
19444
19444
  this._handledTaskIds = /* @__PURE__ */ new Set();
19445
19445
  this._userId = null;
19446
+ this._statusClearTimer = null;
19447
+ this._typingInterval = null;
19448
+ this._lastChatChannel = null;
19446
19449
  this.config = config;
19447
19450
  this.client = new AbracadabraClient({
19448
19451
  url: config.url,
@@ -19537,7 +19540,8 @@ var AbracadabraMCPServer = class {
19537
19540
  provider.awareness.setLocalStateField("user", {
19538
19541
  name: this.agentName,
19539
19542
  color: this.agentColor,
19540
- publicKey: this._userId
19543
+ publicKey: this._userId,
19544
+ isAgent: true
19541
19545
  });
19542
19546
  const conn = {
19543
19547
  doc,
@@ -19590,7 +19594,8 @@ var AbracadabraMCPServer = class {
19590
19594
  childProvider.awareness.setLocalStateField("user", {
19591
19595
  name: this.agentName,
19592
19596
  color: this.agentColor,
19593
- publicKey: this._userId
19597
+ publicKey: this._userId,
19598
+ isAgent: true
19594
19599
  });
19595
19600
  this.childCache.set(docId, {
19596
19601
  provider: childProvider,
@@ -19657,6 +19662,7 @@ var AbracadabraMCPServer = class {
19657
19662
  const user = state["user"];
19658
19663
  const senderName = user && typeof user === "object" && typeof user.name === "string" ? user.name : "Unknown";
19659
19664
  console.error(`[abracadabra-mcp] Handling ai:task id=${id} from ${senderName}: ${text.slice(0, 80)}`);
19665
+ this.setAutoStatus("thinking");
19660
19666
  this._dispatchAiTask({
19661
19667
  id,
19662
19668
  text,
@@ -19721,6 +19727,18 @@ var AbracadabraMCPServer = class {
19721
19727
  const parts = channel.split(":");
19722
19728
  if (parts.length === 3 && parts[1] !== this._userId && parts[2] !== this._userId) return;
19723
19729
  }
19730
+ if (channel) {
19731
+ const rootProvider = this._activeConnection?.provider;
19732
+ if (rootProvider) rootProvider.sendStateless(JSON.stringify({
19733
+ type: "chat:mark_read",
19734
+ channel,
19735
+ timestamp: Math.floor(Date.now() / 1e3)
19736
+ }));
19737
+ this._lastChatChannel = channel;
19738
+ this.sendTypingIndicator(channel);
19739
+ this._startTypingInterval(channel);
19740
+ }
19741
+ this.setAutoStatus("thinking");
19724
19742
  await this._serverRef.notification({
19725
19743
  method: "notifications/claude/channel",
19726
19744
  params: {
@@ -19738,8 +19756,65 @@ var AbracadabraMCPServer = class {
19738
19756
  console.error(`[abracadabra-mcp] Chat message from ${data.sender_name ?? "unknown"} on ${channel}`);
19739
19757
  } catch {}
19740
19758
  }
19759
+ /**
19760
+ * Set the agent's status in root awareness with auto-clear after idle.
19761
+ */
19762
+ setAutoStatus(status, docId) {
19763
+ const provider = this._activeConnection?.provider;
19764
+ if (!provider) return;
19765
+ if (this._statusClearTimer) {
19766
+ clearTimeout(this._statusClearTimer);
19767
+ this._statusClearTimer = null;
19768
+ }
19769
+ provider.awareness.setLocalStateField("status", status);
19770
+ if (docId !== void 0) provider.awareness.setLocalStateField("docId", docId);
19771
+ if (!status) this._stopTypingInterval();
19772
+ if (status) this._statusClearTimer = setTimeout(() => {
19773
+ provider.awareness.setLocalStateField("status", null);
19774
+ provider.awareness.setLocalStateField("activeToolCall", null);
19775
+ this._stopTypingInterval();
19776
+ }, 3e4);
19777
+ }
19778
+ /** Re-send typing indicator every 2s so dashboard keeps showing it (expires at 3s). */
19779
+ _startTypingInterval(channel) {
19780
+ this._stopTypingInterval();
19781
+ this._typingInterval = setInterval(() => {
19782
+ this.sendTypingIndicator(channel);
19783
+ }, 2e3);
19784
+ }
19785
+ _stopTypingInterval() {
19786
+ if (this._typingInterval) {
19787
+ clearInterval(this._typingInterval);
19788
+ this._typingInterval = null;
19789
+ }
19790
+ this._lastChatChannel = null;
19791
+ }
19792
+ /**
19793
+ * Broadcast which tool the agent is currently executing.
19794
+ * Dashboard renders this as a ChatTool indicator.
19795
+ */
19796
+ setActiveToolCall(toolCall) {
19797
+ this._activeConnection?.provider?.awareness.setLocalStateField("activeToolCall", toolCall);
19798
+ }
19799
+ /**
19800
+ * Send a typing indicator to a chat channel.
19801
+ */
19802
+ sendTypingIndicator(channel) {
19803
+ const rootProvider = this._activeConnection?.provider;
19804
+ if (!rootProvider) return;
19805
+ rootProvider.sendStateless(JSON.stringify({
19806
+ type: "chat:typing",
19807
+ channel,
19808
+ sender_name: this.agentName
19809
+ }));
19810
+ }
19741
19811
  /** Graceful shutdown. */
19742
19812
  async destroy() {
19813
+ this._stopTypingInterval();
19814
+ if (this._statusClearTimer) {
19815
+ clearTimeout(this._statusClearTimer);
19816
+ this._statusClearTimer = null;
19817
+ }
19743
19818
  if (this.evictionTimer) {
19744
19819
  clearInterval(this.evictionTimer);
19745
19820
  this.evictionTimer = null;
@@ -19891,12 +19966,20 @@ function registerTreeTools(mcp, server) {
19891
19966
  type: z.string().optional().describe("Page type: \"doc\", \"kanban\", \"calendar\", \"table\", \"outline\", \"gallery\", \"slides\", \"timeline\", \"whiteboard\", \"map\", \"dashboard\", \"mindmap\", \"graph\". Omit to inherit parent view."),
19892
19967
  meta: z.record(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.")
19893
19968
  }, async ({ parentId, label, type, meta }) => {
19969
+ server.setAutoStatus("creating");
19970
+ server.setActiveToolCall({
19971
+ name: "create_document",
19972
+ target: label
19973
+ });
19894
19974
  const treeMap = server.getTreeMap();
19895
19975
  const rootDoc = server.rootDocument;
19896
- if (!treeMap || !rootDoc) return { content: [{
19897
- type: "text",
19898
- text: "Not connected"
19899
- }] };
19976
+ if (!treeMap || !rootDoc) {
19977
+ server.setActiveToolCall(null);
19978
+ return { content: [{
19979
+ type: "text",
19980
+ text: "Not connected"
19981
+ }] };
19982
+ }
19900
19983
  const id = crypto.randomUUID();
19901
19984
  const normalizedParent = normalizeRootId(parentId, server);
19902
19985
  const now = Date.now();
@@ -19911,6 +19994,7 @@ function registerTreeTools(mcp, server) {
19911
19994
  updatedAt: now
19912
19995
  });
19913
19996
  });
19997
+ server.setActiveToolCall(null);
19914
19998
  return { content: [{
19915
19999
  type: "text",
19916
20000
  text: JSON.stringify({
@@ -21145,6 +21229,11 @@ function yjsToMarkdown(fragment) {
21145
21229
  function registerContentTools(mcp, server) {
21146
21230
  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: z.string().describe("Document ID to read.") }, async ({ docId }) => {
21147
21231
  try {
21232
+ server.setAutoStatus("reading", docId);
21233
+ server.setActiveToolCall({
21234
+ name: "read_document",
21235
+ target: docId
21236
+ });
21148
21237
  const { title, markdown } = yjsToMarkdown((await server.getChildProvider(docId)).document.getXmlFragment("default"));
21149
21238
  server.setFocusedDoc(docId);
21150
21239
  server.setDocCursor(docId, 0);
@@ -21170,6 +21259,7 @@ function registerContentTools(mcp, server) {
21170
21259
  });
21171
21260
  children.sort((a, b) => (treeMap.get(a.id)?.order ?? 0) - (treeMap.get(b.id)?.order ?? 0));
21172
21261
  }
21262
+ server.setActiveToolCall(null);
21173
21263
  const result = {
21174
21264
  label,
21175
21265
  type,
@@ -21182,6 +21272,7 @@ function registerContentTools(mcp, server) {
21182
21272
  text: JSON.stringify(result, null, 2)
21183
21273
  }] };
21184
21274
  } catch (error) {
21275
+ server.setActiveToolCall(null);
21185
21276
  return {
21186
21277
  content: [{
21187
21278
  type: "text",
@@ -21197,6 +21288,11 @@ function registerContentTools(mcp, server) {
21197
21288
  mode: z.enum(["replace", "append"]).optional().describe("Write mode. \"replace\" clears existing content first (default). \"append\" adds to the end.")
21198
21289
  }, async ({ docId, markdown, mode }) => {
21199
21290
  try {
21291
+ server.setAutoStatus("writing", docId);
21292
+ server.setActiveToolCall({
21293
+ name: "write_document",
21294
+ target: docId
21295
+ });
21200
21296
  const writeMode = mode ?? "replace";
21201
21297
  const doc = (await server.getChildProvider(docId)).document;
21202
21298
  const fragment = doc.getXmlFragment("default");
@@ -21226,11 +21322,13 @@ function registerContentTools(mcp, server) {
21226
21322
  populateYDocFromMarkdown(fragment, body || markdown, title || "Untitled");
21227
21323
  server.setFocusedDoc(docId);
21228
21324
  server.setDocCursor(docId, fragment.length);
21325
+ server.setActiveToolCall(null);
21229
21326
  return { content: [{
21230
21327
  type: "text",
21231
21328
  text: `Document ${docId} updated (${writeMode} mode)`
21232
21329
  }] };
21233
21330
  } catch (error) {
21331
+ server.setActiveToolCall(null);
21234
21332
  return {
21235
21333
  content: [{
21236
21334
  type: "text",
@@ -21513,15 +21611,23 @@ function registerChannelTools(mcp, server) {
21513
21611
  task_id: z.string().optional().describe("If replying to an ai:task, the task ID to clear from awareness.")
21514
21612
  }, async ({ doc_id, text, task_id }) => {
21515
21613
  try {
21614
+ server.setAutoStatus("writing", doc_id);
21615
+ server.setActiveToolCall({
21616
+ name: "reply",
21617
+ target: doc_id
21618
+ });
21516
21619
  const treeMap = server.getTreeMap();
21517
21620
  const rootDoc = server.rootDocument;
21518
- if (!treeMap || !rootDoc) return {
21519
- content: [{
21520
- type: "text",
21521
- text: "Not connected"
21522
- }],
21523
- isError: true
21524
- };
21621
+ if (!treeMap || !rootDoc) {
21622
+ server.setActiveToolCall(null);
21623
+ return {
21624
+ content: [{
21625
+ type: "text",
21626
+ text: "Not connected"
21627
+ }],
21628
+ isError: true
21629
+ };
21630
+ }
21525
21631
  const label = `AI Reply — ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)}: ${text.slice(0, 40).replace(/\n/g, " ")}`;
21526
21632
  const replyId = crypto.randomUUID();
21527
21633
  const now = Date.now();
@@ -21537,6 +21643,7 @@ function registerChannelTools(mcp, server) {
21537
21643
  });
21538
21644
  populateYDocFromMarkdown((await server.getChildProvider(replyId)).document, text);
21539
21645
  if (task_id) server.clearAiTask(task_id);
21646
+ server.setActiveToolCall(null);
21540
21647
  return { content: [{
21541
21648
  type: "text",
21542
21649
  text: JSON.stringify({
@@ -21545,6 +21652,7 @@ function registerChannelTools(mcp, server) {
21545
21652
  })
21546
21653
  }] };
21547
21654
  } catch (error) {
21655
+ server.setActiveToolCall(null);
21548
21656
  return {
21549
21657
  content: [{
21550
21658
  type: "text",
@@ -21573,6 +21681,8 @@ function registerChannelTools(mcp, server) {
21573
21681
  content: text,
21574
21682
  sender_name: server.agentName
21575
21683
  }));
21684
+ server.setAutoStatus(null);
21685
+ server.setActiveToolCall(null);
21576
21686
  return { content: [{
21577
21687
  type: "text",
21578
21688
  text: `Sent to ${channel}`