@abraca/mcp 1.6.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,
@@ -20149,6 +20204,17 @@ var AbracadabraMCPServer = class {
20149
20204
  getTrashMap() {
20150
20205
  return this._activeConnection?.doc.getMap("doc-trash") ?? null;
20151
20206
  }
20207
+ /** Get plugin names enabled in the active space via space-plugins Y.Map. */
20208
+ getEnabledPluginNames() {
20209
+ const doc = this._activeConnection?.doc;
20210
+ if (!doc) return [];
20211
+ const pluginsMap = doc.getMap("space-plugins");
20212
+ const names = [];
20213
+ pluginsMap.forEach((value, key) => {
20214
+ if ((value?.toJSON ? value.toJSON() : value)?.enabled) names.push(key);
20215
+ });
20216
+ return names;
20217
+ }
20152
20218
  /**
20153
20219
  * Get or create a child provider for a given document ID.
20154
20220
  * Caches providers and waits for sync before returning.
@@ -20227,6 +20293,7 @@ var AbracadabraMCPServer = class {
20227
20293
  _observeRootAwareness(provider) {
20228
20294
  const selfId = provider.awareness.clientID;
20229
20295
  provider.awareness.on("change", () => {
20296
+ if (this.triggerMode === "mention") return;
20230
20297
  const states = provider.awareness.getStates();
20231
20298
  for (const [clientId, state] of states) {
20232
20299
  if (clientId === selfId) continue;
@@ -20239,6 +20306,7 @@ var AbracadabraMCPServer = class {
20239
20306
  const user = state["user"];
20240
20307
  const senderName = user && typeof user === "object" && typeof user.name === "string" ? user.name : "Unknown";
20241
20308
  console.error(`[abracadabra-mcp] Handling ai:task id=${id} from ${senderName}: ${text.slice(0, 80)}`);
20309
+ this._beginTurn();
20242
20310
  this.setAutoStatus("thinking");
20243
20311
  this._dispatchAiTask({
20244
20312
  id,
@@ -20301,9 +20369,28 @@ var AbracadabraMCPServer = class {
20301
20369
  if (data.sender_id && data.sender_id === this._userId) return;
20302
20370
  const channel = data.channel;
20303
20371
  const docId = channel?.startsWith("group:") ? channel.slice(6) : "";
20304
- if (channel?.startsWith("dm:")) {
20372
+ const isDM = channel?.startsWith("dm:") ?? false;
20373
+ const isGroup = channel?.startsWith("group:") ?? false;
20374
+ if (isDM) {
20305
20375
  const parts = channel.split(":");
20306
- 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
+ }
20307
20394
  }
20308
20395
  if (channel) {
20309
20396
  const rootProvider = this._activeConnection?.provider;
@@ -20313,14 +20400,13 @@ var AbracadabraMCPServer = class {
20313
20400
  timestamp: Math.floor(Date.now() / 1e3)
20314
20401
  }));
20315
20402
  this._lastChatChannel = channel;
20316
- this.sendTypingIndicator(channel);
20317
- this._startTypingInterval(channel);
20318
20403
  }
20404
+ this._beginTurn();
20319
20405
  this.setAutoStatus("thinking");
20320
20406
  await this._serverRef.notification({
20321
20407
  method: "notifications/claude/channel",
20322
20408
  params: {
20323
- content: data.content ?? "",
20409
+ content: dispatchContent,
20324
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.`,
20325
20411
  meta: {
20326
20412
  source: "abracadabra",
@@ -20351,13 +20437,35 @@ var AbracadabraMCPServer = class {
20351
20437
  if (docId !== void 0) provider.awareness.setLocalStateField("docId", docId);
20352
20438
  const context = status ? statusContext !== void 0 ? statusContext : this._lastChatChannel : null;
20353
20439
  provider.awareness.setLocalStateField("statusContext", context ?? null);
20354
- 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
+ }
20355
20447
  if (status) this._statusClearTimer = setTimeout(() => {
20356
20448
  provider.awareness.setLocalStateField("status", null);
20357
20449
  provider.awareness.setLocalStateField("activeToolCall", null);
20358
20450
  provider.awareness.setLocalStateField("statusContext", null);
20451
+ provider.awareness.setLocalStateField("turnId", null);
20452
+ this._toolHistory = [];
20453
+ provider.awareness.setLocalStateField("toolHistory", []);
20359
20454
  this._stopTypingInterval();
20360
- }, 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());
20361
20469
  }
20362
20470
  /** Re-send typing indicator every 2s so dashboard keeps showing it (expires at 3s). */
20363
20471
  _startTypingInterval(channel) {
@@ -20375,10 +20483,28 @@ var AbracadabraMCPServer = class {
20375
20483
  }
20376
20484
  /**
20377
20485
  * Broadcast which tool the agent is currently executing.
20378
- * 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.
20379
20493
  */
20380
20494
  setActiveToolCall(toolCall) {
20381
- 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
+ }
20382
20508
  }
20383
20509
  /**
20384
20510
  * Send a typing indicator to a chat channel.
@@ -20409,8 +20535,11 @@ var AbracadabraMCPServer = class {
20409
20535
  conn.provider.awareness.setLocalStateField("status", null);
20410
20536
  conn.provider.awareness.setLocalStateField("activeToolCall", null);
20411
20537
  conn.provider.awareness.setLocalStateField("statusContext", null);
20538
+ conn.provider.awareness.setLocalStateField("turnId", null);
20539
+ conn.provider.awareness.setLocalStateField("toolHistory", []);
20412
20540
  conn.provider.destroy();
20413
20541
  }
20542
+ this._toolHistory = [];
20414
20543
  this._spaceConnections.clear();
20415
20544
  this._activeConnection = null;
20416
20545
  console.error("[abracadabra-mcp] Shutdown complete");
@@ -20435,6 +20564,724 @@ function docToSpaceMeta(doc) {
20435
20564
  };
20436
20565
  }
20437
20566
 
20567
+ //#endregion
20568
+ //#region packages/mcp/src/converters/page-types.ts
20569
+ const PAGE_TYPES = {
20570
+ doc: {
20571
+ key: "doc",
20572
+ label: "Document",
20573
+ icon: "file-text",
20574
+ description: "Rich text document with real-time collaboration",
20575
+ core: true,
20576
+ supportsChildren: true
20577
+ },
20578
+ kanban: {
20579
+ key: "kanban",
20580
+ label: "Kanban",
20581
+ icon: "kanban",
20582
+ description: "Drag-and-drop task board with columns and cards",
20583
+ core: true,
20584
+ supportsChildren: true,
20585
+ childLabel: "Column",
20586
+ grandchildLabel: "Card",
20587
+ defaultMetaFields: [{
20588
+ type: "select",
20589
+ key: "kanbanColumnWidth",
20590
+ options: [
20591
+ "narrow",
20592
+ "default",
20593
+ "wide"
20594
+ ],
20595
+ label: "Column Width"
20596
+ }]
20597
+ },
20598
+ gallery: {
20599
+ key: "gallery",
20600
+ label: "Gallery",
20601
+ icon: "images",
20602
+ description: "Visual grid of items with rich content",
20603
+ core: true,
20604
+ supportsChildren: true,
20605
+ childLabel: "Item",
20606
+ metaSchema: [
20607
+ {
20608
+ type: "location",
20609
+ latKey: "geoLat",
20610
+ lngKey: "geoLng",
20611
+ label: "Location"
20612
+ },
20613
+ {
20614
+ type: "datetime",
20615
+ key: "datetimeStart",
20616
+ label: "Date"
20617
+ },
20618
+ {
20619
+ type: "tags",
20620
+ key: "tags",
20621
+ label: "Tags"
20622
+ },
20623
+ {
20624
+ type: "rating",
20625
+ key: "rating",
20626
+ max: 5,
20627
+ label: "Rating"
20628
+ },
20629
+ {
20630
+ type: "icon",
20631
+ key: "icon",
20632
+ label: "Icon"
20633
+ },
20634
+ {
20635
+ type: "colorPreset",
20636
+ key: "color",
20637
+ presets: [
20638
+ "#6366f1",
20639
+ "#ec4899",
20640
+ "#f97316",
20641
+ "#22c55e",
20642
+ "#3b82f6",
20643
+ "#a855f7"
20644
+ ],
20645
+ label: "Color"
20646
+ }
20647
+ ],
20648
+ defaultMetaFields: [
20649
+ {
20650
+ type: "number",
20651
+ key: "galleryColumns",
20652
+ min: 1,
20653
+ max: 6,
20654
+ step: 1,
20655
+ label: "Columns"
20656
+ },
20657
+ {
20658
+ type: "select",
20659
+ key: "galleryAspect",
20660
+ options: [
20661
+ "square",
20662
+ "4:3",
20663
+ "3:2",
20664
+ "16:9",
20665
+ "free"
20666
+ ],
20667
+ label: "Aspect Ratio"
20668
+ },
20669
+ {
20670
+ type: "select",
20671
+ key: "galleryCardStyle",
20672
+ options: [
20673
+ "default",
20674
+ "compact",
20675
+ "detailed"
20676
+ ],
20677
+ label: "Card Style"
20678
+ },
20679
+ {
20680
+ type: "toggle",
20681
+ key: "galleryShowLabels",
20682
+ label: "Show Labels"
20683
+ },
20684
+ {
20685
+ type: "select",
20686
+ key: "gallerySortBy",
20687
+ options: [
20688
+ "manual",
20689
+ "date",
20690
+ "name",
20691
+ "rating"
20692
+ ],
20693
+ label: "Sort"
20694
+ }
20695
+ ]
20696
+ },
20697
+ table: {
20698
+ key: "table",
20699
+ label: "Table",
20700
+ icon: "table",
20701
+ description: "Collaborative spreadsheet with custom fields",
20702
+ core: true,
20703
+ supportsChildren: true,
20704
+ childLabel: "Column",
20705
+ grandchildLabel: "Row",
20706
+ defaultMetaFields: [{
20707
+ type: "select",
20708
+ key: "tableMode",
20709
+ options: ["hierarchy", "flat"],
20710
+ label: "Mode"
20711
+ }, {
20712
+ type: "select",
20713
+ key: "tableSortDir",
20714
+ options: ["asc", "desc"],
20715
+ label: "Sort Direction"
20716
+ }]
20717
+ },
20718
+ outline: {
20719
+ key: "outline",
20720
+ label: "Outline",
20721
+ icon: "list-tree",
20722
+ description: "Hierarchical outline with keyboard navigation",
20723
+ core: true,
20724
+ supportsChildren: true,
20725
+ childLabel: "Item",
20726
+ defaultDepth: -1
20727
+ },
20728
+ checklist: {
20729
+ key: "checklist",
20730
+ label: "Checklist",
20731
+ icon: "check-square",
20732
+ description: "Collaborative checklist with sub-tasks, drag-and-drop, and due dates",
20733
+ core: true,
20734
+ supportsChildren: true,
20735
+ childLabel: "Task",
20736
+ defaultDepth: -1,
20737
+ metaSchema: [
20738
+ {
20739
+ type: "toggle",
20740
+ key: "checked",
20741
+ label: "Done"
20742
+ },
20743
+ {
20744
+ type: "select",
20745
+ key: "priority",
20746
+ options: [
20747
+ "none",
20748
+ "low",
20749
+ "medium",
20750
+ "high"
20751
+ ],
20752
+ label: "Priority"
20753
+ },
20754
+ {
20755
+ type: "date",
20756
+ key: "dateEnd",
20757
+ label: "Due date"
20758
+ }
20759
+ ],
20760
+ defaultMetaFields: [{
20761
+ type: "select",
20762
+ key: "checklistFilter",
20763
+ options: [
20764
+ "all",
20765
+ "active",
20766
+ "completed"
20767
+ ],
20768
+ label: "Filter"
20769
+ }, {
20770
+ type: "select",
20771
+ key: "checklistSort",
20772
+ options: [
20773
+ "manual",
20774
+ "priority",
20775
+ "due"
20776
+ ],
20777
+ label: "Sort"
20778
+ }]
20779
+ },
20780
+ graph: {
20781
+ key: "graph",
20782
+ label: "Graph",
20783
+ icon: "git-fork",
20784
+ description: "Force-directed knowledge graph — full document tree as nodes & edges",
20785
+ core: true,
20786
+ supportsChildren: true,
20787
+ childLabel: "Node",
20788
+ defaultMetaFields: [{
20789
+ type: "toggle",
20790
+ key: "showRefEdges",
20791
+ label: "Show Ref Edges",
20792
+ default: true
20793
+ }]
20794
+ },
20795
+ timeline: {
20796
+ key: "timeline",
20797
+ label: "Timeline",
20798
+ icon: "gantt-chart",
20799
+ description: "Gantt-style project timeline with epics and tasks",
20800
+ core: true,
20801
+ supportsChildren: true,
20802
+ childLabel: "Epic",
20803
+ grandchildLabel: "Task",
20804
+ metaSchema: [
20805
+ {
20806
+ type: "daterange",
20807
+ startKey: "dateStart",
20808
+ endKey: "dateEnd"
20809
+ },
20810
+ {
20811
+ type: "slider",
20812
+ key: "taskProgress",
20813
+ min: 0,
20814
+ max: 100,
20815
+ label: "Progress"
20816
+ },
20817
+ {
20818
+ type: "colorPreset",
20819
+ key: "color",
20820
+ presets: [
20821
+ "#6366f1",
20822
+ "#818cf8",
20823
+ "#f97316",
20824
+ "#22c55e",
20825
+ "#3b82f6",
20826
+ "#a855f7"
20827
+ ],
20828
+ label: "Color"
20829
+ }
20830
+ ]
20831
+ },
20832
+ calendar: {
20833
+ key: "calendar",
20834
+ label: "Calendar",
20835
+ icon: "calendar",
20836
+ description: "Event calendar with month, week, and day views",
20837
+ core: true,
20838
+ supportsChildren: true,
20839
+ childLabel: "Event",
20840
+ metaSchema: [
20841
+ {
20842
+ type: "datetimerange",
20843
+ startKey: "datetimeStart",
20844
+ endKey: "datetimeEnd",
20845
+ allDayKey: "allDay"
20846
+ },
20847
+ {
20848
+ type: "colorPreset",
20849
+ key: "color",
20850
+ presets: [
20851
+ "#6366f1",
20852
+ "#ec4899",
20853
+ "#f97316",
20854
+ "#22c55e",
20855
+ "#3b82f6",
20856
+ "#a855f7"
20857
+ ],
20858
+ label: "Color"
20859
+ },
20860
+ {
20861
+ type: "icon",
20862
+ key: "icon",
20863
+ label: "Icon"
20864
+ }
20865
+ ],
20866
+ defaultMetaFields: [
20867
+ {
20868
+ type: "select",
20869
+ key: "calendarWeekStart",
20870
+ options: ["sun", "mon"],
20871
+ label: "Week Starts"
20872
+ },
20873
+ {
20874
+ type: "select",
20875
+ key: "calendarView",
20876
+ options: [
20877
+ "month",
20878
+ "week",
20879
+ "day"
20880
+ ],
20881
+ label: "Default View"
20882
+ },
20883
+ {
20884
+ type: "toggle",
20885
+ key: "calendarShowWeekNumbers",
20886
+ label: "Show Week Numbers"
20887
+ }
20888
+ ]
20889
+ },
20890
+ map: {
20891
+ key: "map",
20892
+ label: "Map",
20893
+ icon: "map",
20894
+ description: "Collaborative world map with shared markers",
20895
+ core: true,
20896
+ supportsChildren: true,
20897
+ childLabel: "Location",
20898
+ defaultMetaFields: [{
20899
+ type: "toggle",
20900
+ key: "mapShowLabels",
20901
+ label: "Show Labels",
20902
+ default: true
20903
+ }]
20904
+ },
20905
+ dashboard: {
20906
+ key: "dashboard",
20907
+ label: "Dashboard",
20908
+ icon: "layout-dashboard",
20909
+ description: "Arrange documents as draggable icons with optional widget views",
20910
+ core: true,
20911
+ supportsChildren: true,
20912
+ childLabel: "Item"
20913
+ },
20914
+ call: {
20915
+ key: "call",
20916
+ label: "Call",
20917
+ icon: "phone",
20918
+ description: "Video call room with live audio and screen sharing",
20919
+ core: true,
20920
+ supportsChildren: false
20921
+ },
20922
+ chart: {
20923
+ key: "chart",
20924
+ label: "Chart",
20925
+ icon: "bar-chart-3",
20926
+ description: "Charts — manual data points or aggregation over document trees",
20927
+ core: true,
20928
+ supportsChildren: true,
20929
+ childLabel: "Data Point",
20930
+ grandchildLabel: "Data Point",
20931
+ metaSchema: [
20932
+ {
20933
+ type: "number",
20934
+ key: "number",
20935
+ step: .01,
20936
+ label: "Value"
20937
+ },
20938
+ {
20939
+ type: "colorPreset",
20940
+ key: "color",
20941
+ presets: [
20942
+ "#6366f1",
20943
+ "#ec4899",
20944
+ "#f97316",
20945
+ "#22c55e",
20946
+ "#3b82f6",
20947
+ "#a855f7",
20948
+ "#14b8a6",
20949
+ "#eab308"
20950
+ ],
20951
+ label: "Color"
20952
+ },
20953
+ {
20954
+ type: "tags",
20955
+ key: "tags",
20956
+ label: "Tags"
20957
+ }
20958
+ ],
20959
+ defaultMetaFields: [
20960
+ {
20961
+ type: "select",
20962
+ key: "chartType",
20963
+ options: [
20964
+ "bar",
20965
+ "stacked bar",
20966
+ "line",
20967
+ "donut",
20968
+ "treemap"
20969
+ ],
20970
+ label: "Chart Type"
20971
+ },
20972
+ {
20973
+ type: "select",
20974
+ key: "chartMetric",
20975
+ options: [
20976
+ "value",
20977
+ "type",
20978
+ "tag",
20979
+ "status",
20980
+ "priority",
20981
+ "activity",
20982
+ "completion"
20983
+ ],
20984
+ label: "Metric"
20985
+ },
20986
+ {
20987
+ type: "select",
20988
+ key: "chartColorScheme",
20989
+ options: [
20990
+ "default",
20991
+ "warm",
20992
+ "cool",
20993
+ "mono"
20994
+ ],
20995
+ label: "Colors"
20996
+ },
20997
+ {
20998
+ type: "number",
20999
+ key: "chartLimit",
21000
+ min: 3,
21001
+ max: 30,
21002
+ step: 1,
21003
+ label: "Max Items"
21004
+ },
21005
+ {
21006
+ type: "toggle",
21007
+ key: "chartShowLegend",
21008
+ label: "Show Legend",
21009
+ default: true
21010
+ },
21011
+ {
21012
+ type: "toggle",
21013
+ key: "chartShowValues",
21014
+ label: "Show Values"
21015
+ }
21016
+ ]
21017
+ },
21018
+ sheets: {
21019
+ key: "sheets",
21020
+ label: "Sheets",
21021
+ icon: "grid-3x3",
21022
+ description: "Spreadsheet — cells, formulas, and formatting in a collaborative grid",
21023
+ core: true,
21024
+ supportsChildren: true,
21025
+ childLabel: "Column",
21026
+ grandchildLabel: "Cell",
21027
+ defaultMetaFields: [
21028
+ {
21029
+ type: "number",
21030
+ key: "sheetsDefaultColWidth",
21031
+ min: 40,
21032
+ max: 500,
21033
+ step: 10,
21034
+ label: "Column Width"
21035
+ },
21036
+ {
21037
+ type: "number",
21038
+ key: "sheetsDefaultRowHeight",
21039
+ min: 20,
21040
+ max: 100,
21041
+ step: 2,
21042
+ label: "Row Height"
21043
+ },
21044
+ {
21045
+ type: "toggle",
21046
+ key: "sheetsShowGridlines",
21047
+ label: "Gridlines"
21048
+ }
21049
+ ]
21050
+ },
21051
+ slides: {
21052
+ key: "slides",
21053
+ label: "Slides",
21054
+ icon: "presentation",
21055
+ description: "Presentation slides with two-axis navigation",
21056
+ core: true,
21057
+ supportsChildren: true,
21058
+ childLabel: "Slide",
21059
+ grandchildLabel: "Sub-slide",
21060
+ metaSchema: [{
21061
+ type: "select",
21062
+ key: "slidesTransition",
21063
+ options: [
21064
+ "none",
21065
+ "fade",
21066
+ "slide"
21067
+ ],
21068
+ label: "Transition"
21069
+ }, {
21070
+ type: "colorPreset",
21071
+ key: "color",
21072
+ presets: [
21073
+ "#6366f1",
21074
+ "#ec4899",
21075
+ "#f97316",
21076
+ "#22c55e",
21077
+ "#3b82f6",
21078
+ "#a855f7"
21079
+ ],
21080
+ label: "Accent"
21081
+ }],
21082
+ defaultMetaFields: [{
21083
+ type: "select",
21084
+ key: "slidesTheme",
21085
+ options: ["dark", "light"],
21086
+ label: "Theme"
21087
+ }]
21088
+ },
21089
+ overview: {
21090
+ key: "overview",
21091
+ label: "Overview",
21092
+ icon: "radar",
21093
+ description: "Space home — activity, people, stats, and health at a glance",
21094
+ core: true,
21095
+ supportsChildren: true,
21096
+ childLabel: "Page"
21097
+ },
21098
+ spatial: {
21099
+ key: "spatial",
21100
+ label: "Spatial",
21101
+ icon: "box",
21102
+ description: "3D scene with collaborative objects and real-time presence",
21103
+ core: false,
21104
+ plugin: "spatial",
21105
+ supportsChildren: true,
21106
+ childLabel: "Object",
21107
+ grandchildLabel: "Part",
21108
+ defaultDepth: -1,
21109
+ metaSchema: [
21110
+ {
21111
+ type: "select",
21112
+ key: "spShape",
21113
+ options: [
21114
+ "box",
21115
+ "sphere",
21116
+ "cylinder",
21117
+ "cone",
21118
+ "plane",
21119
+ "torus",
21120
+ "glb"
21121
+ ],
21122
+ label: "Shape"
21123
+ },
21124
+ {
21125
+ type: "colorPreset",
21126
+ key: "color",
21127
+ presets: [
21128
+ "#6366f1",
21129
+ "#ef4444",
21130
+ "#22c55e",
21131
+ "#3b82f6",
21132
+ "#f97316",
21133
+ "#a855f7",
21134
+ "#ec4899",
21135
+ "#14b8a6"
21136
+ ],
21137
+ label: "Color"
21138
+ },
21139
+ {
21140
+ type: "slider",
21141
+ key: "spOpacity",
21142
+ min: 0,
21143
+ max: 100,
21144
+ label: "Opacity"
21145
+ },
21146
+ {
21147
+ type: "number",
21148
+ key: "spX",
21149
+ step: .1,
21150
+ label: "X"
21151
+ },
21152
+ {
21153
+ type: "number",
21154
+ key: "spY",
21155
+ step: .1,
21156
+ label: "Y"
21157
+ },
21158
+ {
21159
+ type: "number",
21160
+ key: "spZ",
21161
+ step: .1,
21162
+ label: "Z"
21163
+ },
21164
+ {
21165
+ type: "number",
21166
+ key: "spRX",
21167
+ min: -180,
21168
+ max: 180,
21169
+ step: 1,
21170
+ label: "Rot X"
21171
+ },
21172
+ {
21173
+ type: "number",
21174
+ key: "spRY",
21175
+ min: -180,
21176
+ max: 180,
21177
+ step: 1,
21178
+ label: "Rot Y"
21179
+ },
21180
+ {
21181
+ type: "number",
21182
+ key: "spRZ",
21183
+ min: -180,
21184
+ max: 180,
21185
+ step: 1,
21186
+ label: "Rot Z"
21187
+ },
21188
+ {
21189
+ type: "number",
21190
+ key: "spSX",
21191
+ min: .01,
21192
+ max: 100,
21193
+ step: .1,
21194
+ label: "Scale X"
21195
+ },
21196
+ {
21197
+ type: "number",
21198
+ key: "spSY",
21199
+ min: .01,
21200
+ max: 100,
21201
+ step: .1,
21202
+ label: "Scale Y"
21203
+ },
21204
+ {
21205
+ type: "number",
21206
+ key: "spSZ",
21207
+ min: .01,
21208
+ max: 100,
21209
+ step: .1,
21210
+ label: "Scale Z"
21211
+ }
21212
+ ],
21213
+ defaultMetaFields: [{
21214
+ type: "toggle",
21215
+ key: "spatialGridVisible",
21216
+ label: "Show Grid",
21217
+ default: true
21218
+ }]
21219
+ },
21220
+ media: {
21221
+ key: "media",
21222
+ label: "Media",
21223
+ icon: "disc-3",
21224
+ description: "Media player with synced listening and playlists",
21225
+ core: false,
21226
+ plugin: "media",
21227
+ supportsChildren: true,
21228
+ childLabel: "Track",
21229
+ defaultDepth: -1,
21230
+ metaSchema: [{
21231
+ type: "tags",
21232
+ key: "tags",
21233
+ label: "Tags"
21234
+ }],
21235
+ defaultMetaFields: [{
21236
+ type: "select",
21237
+ key: "mediaRepeat",
21238
+ options: [
21239
+ "off",
21240
+ "all",
21241
+ "one"
21242
+ ],
21243
+ label: "Repeat"
21244
+ }, {
21245
+ type: "toggle",
21246
+ key: "mediaShuffle",
21247
+ label: "Shuffle"
21248
+ }]
21249
+ },
21250
+ coder: {
21251
+ key: "coder",
21252
+ label: "Coder",
21253
+ icon: "code-2",
21254
+ description: "Collaborative multi-file coding environment",
21255
+ core: false,
21256
+ plugin: "coder",
21257
+ supportsChildren: true,
21258
+ childLabel: "File",
21259
+ defaultDepth: -1,
21260
+ metaSchema: [{
21261
+ type: "select",
21262
+ key: "fileType",
21263
+ options: [
21264
+ "vue",
21265
+ "ts",
21266
+ "js",
21267
+ "css",
21268
+ "json",
21269
+ "folder"
21270
+ ],
21271
+ label: "Type"
21272
+ }, {
21273
+ type: "toggle",
21274
+ key: "entry",
21275
+ label: "Entry Point"
21276
+ }]
21277
+ }
21278
+ };
21279
+ const TYPE_ALIASES = { desktop: "dashboard" };
21280
+ function resolvePageType(key) {
21281
+ if (!key) return void 0;
21282
+ return PAGE_TYPES[TYPE_ALIASES[key] ?? key];
21283
+ }
21284
+
20438
21285
  //#endregion
20439
21286
  //#region packages/mcp/src/tools/tree.ts
20440
21287
  /**
@@ -20508,16 +21355,12 @@ function registerTreeTools(mcp, server) {
20508
21355
  server.setAutoStatus("reading");
20509
21356
  server.setActiveToolCall({ name: "list_documents" });
20510
21357
  const treeMap = server.getTreeMap();
20511
- if (!treeMap) {
20512
- server.setActiveToolCall(null);
20513
- return { content: [{
20514
- type: "text",
20515
- text: "Not connected"
20516
- }] };
20517
- }
21358
+ if (!treeMap) return { content: [{
21359
+ type: "text",
21360
+ text: "Not connected"
21361
+ }] };
20518
21362
  const targetId = normalizeRootId(parentId, server);
20519
21363
  const children = childrenOf$1(readEntries$1(treeMap), targetId);
20520
- server.setActiveToolCall(null);
20521
21364
  return { content: [{
20522
21365
  type: "text",
20523
21366
  text: JSON.stringify(children, null, 2)
@@ -20530,17 +21373,13 @@ function registerTreeTools(mcp, server) {
20530
21373
  server.setAutoStatus("reading");
20531
21374
  server.setActiveToolCall({ name: "get_document_tree" });
20532
21375
  const treeMap = server.getTreeMap();
20533
- if (!treeMap) {
20534
- server.setActiveToolCall(null);
20535
- return { content: [{
20536
- type: "text",
20537
- text: "Not connected"
20538
- }] };
20539
- }
21376
+ if (!treeMap) return { content: [{
21377
+ type: "text",
21378
+ text: "Not connected"
21379
+ }] };
20540
21380
  const targetId = normalizeRootId(rootId, server);
20541
21381
  const maxDepth = depth ?? 3;
20542
21382
  const tree = buildTree$1(readEntries$1(treeMap), targetId, maxDepth);
20543
- server.setActiveToolCall(null);
20544
21383
  return { content: [{
20545
21384
  type: "text",
20546
21385
  text: JSON.stringify(tree, null, 2)
@@ -20556,13 +21395,10 @@ function registerTreeTools(mcp, server) {
20556
21395
  target: query
20557
21396
  });
20558
21397
  const treeMap = server.getTreeMap();
20559
- if (!treeMap) {
20560
- server.setActiveToolCall(null);
20561
- return { content: [{
20562
- type: "text",
20563
- text: "Not connected"
20564
- }] };
20565
- }
21398
+ if (!treeMap) return { content: [{
21399
+ type: "text",
21400
+ text: "Not connected"
21401
+ }] };
20566
21402
  const entries = readEntries$1(treeMap);
20567
21403
  const lowerQuery = query.toLowerCase();
20568
21404
  const normalizedRoot = normalizeRootId(rootId, server);
@@ -20587,7 +21423,6 @@ function registerTreeTools(mcp, server) {
20587
21423
  path
20588
21424
  };
20589
21425
  });
20590
- server.setActiveToolCall(null);
20591
21426
  if (results.length === 0) return { content: [{
20592
21427
  type: "text",
20593
21428
  text: `No documents found matching "${query}". Try get_document_tree to see the full hierarchy.`
@@ -20600,8 +21435,8 @@ function registerTreeTools(mcp, server) {
20600
21435
  mcp.tool("create_document", "Create a new document in the tree. Returns the new document ID.", {
20601
21436
  parentId: zod.z.string().optional().describe("Parent document ID. Omit for top-level pages. Use a document ID for nested/child pages."),
20602
21437
  label: zod.z.string().describe("Display name / title for the document."),
20603
- type: zod.z.string().optional().describe("Page type — sets how this document renders. \"doc\" (rich text), \"kanban\" (columns → cards), \"table\" (columns → rows with custom fields), \"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), \"spatial\" (3D scene with spShape/spX/spY/spZ), \"media\" (audio/video player with playlists), \"slides\" (presentation with transitions), \"chart\" (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). Omit to inherit parent view. Only set on the parent page, NEVER on child items."),
20604
- 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.")
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."),
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.")
20605
21440
  }, async ({ parentId, label, type, meta }) => {
20606
21441
  server.setAutoStatus("creating");
20607
21442
  server.setActiveToolCall({
@@ -20610,13 +21445,10 @@ function registerTreeTools(mcp, server) {
20610
21445
  });
20611
21446
  const treeMap = server.getTreeMap();
20612
21447
  const rootDoc = server.rootDocument;
20613
- if (!treeMap || !rootDoc) {
20614
- server.setActiveToolCall(null);
20615
- return { content: [{
20616
- type: "text",
20617
- text: "Not connected"
20618
- }] };
20619
- }
21448
+ if (!treeMap || !rootDoc) return { content: [{
21449
+ type: "text",
21450
+ text: "Not connected"
21451
+ }] };
20620
21452
  const id = crypto.randomUUID();
20621
21453
  const normalizedParent = normalizeRootId(parentId, server);
20622
21454
  const now = Date.now();
@@ -20632,7 +21464,6 @@ function registerTreeTools(mcp, server) {
20632
21464
  });
20633
21465
  });
20634
21466
  server.setFocusedDoc(id);
20635
- server.setActiveToolCall(null);
20636
21467
  return { content: [{
20637
21468
  type: "text",
20638
21469
  text: JSON.stringify({
@@ -20653,28 +21484,21 @@ function registerTreeTools(mcp, server) {
20653
21484
  target: id
20654
21485
  });
20655
21486
  const treeMap = server.getTreeMap();
20656
- if (!treeMap) {
20657
- server.setActiveToolCall(null);
20658
- return { content: [{
20659
- type: "text",
20660
- text: "Not connected"
20661
- }] };
20662
- }
21487
+ if (!treeMap) return { content: [{
21488
+ type: "text",
21489
+ text: "Not connected"
21490
+ }] };
20663
21491
  const raw = treeMap.get(id);
20664
- if (!raw) {
20665
- server.setActiveToolCall(null);
20666
- return { content: [{
20667
- type: "text",
20668
- text: `Document ${id} not found`
20669
- }] };
20670
- }
21492
+ if (!raw) return { content: [{
21493
+ type: "text",
21494
+ text: `Document ${id} not found`
21495
+ }] };
20671
21496
  const entry = toPlain(raw);
20672
21497
  treeMap.set(id, {
20673
21498
  ...entry,
20674
21499
  label,
20675
21500
  updatedAt: Date.now()
20676
21501
  });
20677
- server.setActiveToolCall(null);
20678
21502
  return { content: [{
20679
21503
  type: "text",
20680
21504
  text: `Renamed to "${label}"`
@@ -20691,21 +21515,15 @@ function registerTreeTools(mcp, server) {
20691
21515
  target: id
20692
21516
  });
20693
21517
  const treeMap = server.getTreeMap();
20694
- if (!treeMap) {
20695
- server.setActiveToolCall(null);
20696
- return { content: [{
20697
- type: "text",
20698
- text: "Not connected"
20699
- }] };
20700
- }
21518
+ if (!treeMap) return { content: [{
21519
+ type: "text",
21520
+ text: "Not connected"
21521
+ }] };
20701
21522
  const raw = treeMap.get(id);
20702
- if (!raw) {
20703
- server.setActiveToolCall(null);
20704
- return { content: [{
20705
- type: "text",
20706
- text: `Document ${id} not found`
20707
- }] };
20708
- }
21523
+ if (!raw) return { content: [{
21524
+ type: "text",
21525
+ text: `Document ${id} not found`
21526
+ }] };
20709
21527
  const entry = toPlain(raw);
20710
21528
  treeMap.set(id, {
20711
21529
  ...entry,
@@ -20713,7 +21531,6 @@ function registerTreeTools(mcp, server) {
20713
21531
  order: order ?? Date.now(),
20714
21532
  updatedAt: Date.now()
20715
21533
  });
20716
- server.setActiveToolCall(null);
20717
21534
  return { content: [{
20718
21535
  type: "text",
20719
21536
  text: `Moved ${id} to parent ${newParentId}`
@@ -20728,13 +21545,10 @@ function registerTreeTools(mcp, server) {
20728
21545
  const treeMap = server.getTreeMap();
20729
21546
  const trashMap = server.getTrashMap();
20730
21547
  const rootDoc = server.rootDocument;
20731
- if (!treeMap || !trashMap || !rootDoc) {
20732
- server.setActiveToolCall(null);
20733
- return { content: [{
20734
- type: "text",
20735
- text: "Not connected"
20736
- }] };
20737
- }
21548
+ if (!treeMap || !trashMap || !rootDoc) return { content: [{
21549
+ type: "text",
21550
+ text: "Not connected"
21551
+ }] };
20738
21552
  const toDelete = [id, ...descendantsOf(readEntries$1(treeMap), id).map((e) => e.id)];
20739
21553
  const now = Date.now();
20740
21554
  rootDoc.transact(() => {
@@ -20753,7 +21567,6 @@ function registerTreeTools(mcp, server) {
20753
21567
  treeMap.delete(nid);
20754
21568
  }
20755
21569
  });
20756
- server.setActiveToolCall(null);
20757
21570
  return { content: [{
20758
21571
  type: "text",
20759
21572
  text: `Deleted ${toDelete.length} document(s)`
@@ -20761,7 +21574,7 @@ function registerTreeTools(mcp, server) {
20761
21574
  });
20762
21575
  mcp.tool("change_document_type", "Change the page type view of a document (data is preserved).", {
20763
21576
  id: zod.z.string().describe("Document ID."),
20764
- type: zod.z.string().describe("New page type: \"doc\", \"kanban\", \"table\", \"calendar\", \"timeline\", \"checklist\", \"outline\", \"gallery\", \"map\", \"graph\", \"dashboard\", \"spatial\", \"media\", \"slides\", \"chart\", \"sheets\", \"overview\", \"call\".")
21577
+ type: zod.z.string().describe("New page type. Core: \"doc\", \"kanban\", \"table\", \"calendar\", \"timeline\", \"checklist\", \"outline\", \"gallery\", \"map\", \"graph\", \"dashboard\", \"slides\", \"chart\", \"sheets\", \"overview\", \"call\". Plugin (require plugin enabled): \"spatial\", \"media\", \"coder\". Switching preserves the tree — children, labels, and meta are all retained; only the view changes.")
20765
21578
  }, async ({ id, type }) => {
20766
21579
  server.setAutoStatus("writing");
20767
21580
  server.setActiveToolCall({
@@ -20769,28 +21582,21 @@ function registerTreeTools(mcp, server) {
20769
21582
  target: id
20770
21583
  });
20771
21584
  const treeMap = server.getTreeMap();
20772
- if (!treeMap) {
20773
- server.setActiveToolCall(null);
20774
- return { content: [{
20775
- type: "text",
20776
- text: "Not connected"
20777
- }] };
20778
- }
21585
+ if (!treeMap) return { content: [{
21586
+ type: "text",
21587
+ text: "Not connected"
21588
+ }] };
20779
21589
  const raw = treeMap.get(id);
20780
- if (!raw) {
20781
- server.setActiveToolCall(null);
20782
- return { content: [{
20783
- type: "text",
20784
- text: `Document ${id} not found`
20785
- }] };
20786
- }
21590
+ if (!raw) return { content: [{
21591
+ type: "text",
21592
+ text: `Document ${id} not found`
21593
+ }] };
20787
21594
  const entry = toPlain(raw);
20788
21595
  treeMap.set(id, {
20789
21596
  ...entry,
20790
21597
  type,
20791
21598
  updatedAt: Date.now()
20792
21599
  });
20793
- server.setActiveToolCall(null);
20794
21600
  return { content: [{
20795
21601
  type: "text",
20796
21602
  text: `Changed type to "${type}"`
@@ -20846,6 +21652,27 @@ function registerTreeTools(mcp, server) {
20846
21652
  }, null, 2)
20847
21653
  }] };
20848
21654
  });
21655
+ mcp.tool("list_page_types", "Enumerate all known Abracadabra page types with their metadata schemas. Returns an array of { key, label, icon, description, core, plugin, supportsChildren, childLabel, grandchildLabel, defaultDepth, metaSchema, defaultMetaFields }. `metaSchema` describes fields that apply to DESCENDANTS (children, grandchildren, ...) of a page of this type. `defaultMetaFields` are view-config fields on the page doc itself. Plugin types (core: false) require the named plugin to be enabled on the server. Use this to discover what meta keys a given renderer supports instead of guessing.", { key: zod.z.string().optional().describe("Filter to a single type by key (e.g. \"kanban\", \"calendar\"). Aliases are resolved (e.g. \"desktop\" → \"dashboard\"). Omit to list all types.") }, async ({ key }) => {
21656
+ if (key) {
21657
+ const resolved = resolvePageType(key);
21658
+ if (!resolved) return { content: [{
21659
+ type: "text",
21660
+ text: `Unknown page type "${key}". Call list_page_types with no args to see all types.`
21661
+ }] };
21662
+ return { content: [{
21663
+ type: "text",
21664
+ text: JSON.stringify(resolved, null, 2)
21665
+ }] };
21666
+ }
21667
+ const all = Object.values(PAGE_TYPES);
21668
+ return { content: [{
21669
+ type: "text",
21670
+ text: JSON.stringify({
21671
+ types: all,
21672
+ aliases: TYPE_ALIASES
21673
+ }, null, 2)
21674
+ }] };
21675
+ });
20849
21676
  }
20850
21677
 
20851
21678
  //#endregion
@@ -22025,7 +22852,6 @@ function registerContentTools(mcp, server) {
22025
22852
  });
22026
22853
  children.sort((a, b) => (treeMap.get(a.id)?.order ?? 0) - (treeMap.get(b.id)?.order ?? 0));
22027
22854
  }
22028
- server.setActiveToolCall(null);
22029
22855
  const result = {
22030
22856
  label,
22031
22857
  type,
@@ -22038,7 +22864,6 @@ function registerContentTools(mcp, server) {
22038
22864
  text: JSON.stringify(result, null, 2)
22039
22865
  }] };
22040
22866
  } catch (error) {
22041
- server.setActiveToolCall(null);
22042
22867
  return {
22043
22868
  content: [{
22044
22869
  type: "text",
@@ -22088,13 +22913,11 @@ function registerContentTools(mcp, server) {
22088
22913
  populateYDocFromMarkdown(fragment, body || markdown, title || "Untitled");
22089
22914
  server.setFocusedDoc(docId);
22090
22915
  server.setDocCursor(docId, fragment.length);
22091
- server.setActiveToolCall(null);
22092
22916
  return { content: [{
22093
22917
  type: "text",
22094
22918
  text: `Document ${docId} updated (${writeMode} mode)`
22095
22919
  }] };
22096
22920
  } catch (error) {
22097
- server.setActiveToolCall(null);
22098
22921
  return {
22099
22922
  content: [{
22100
22923
  type: "text",
@@ -22116,22 +22939,15 @@ function registerMetaTools(mcp, server) {
22116
22939
  target: docId
22117
22940
  });
22118
22941
  const treeMap = server.getTreeMap();
22119
- if (!treeMap) {
22120
- server.setActiveToolCall(null);
22121
- return { content: [{
22122
- type: "text",
22123
- text: "Not connected"
22124
- }] };
22125
- }
22942
+ if (!treeMap) return { content: [{
22943
+ type: "text",
22944
+ text: "Not connected"
22945
+ }] };
22126
22946
  const entry = treeMap.get(docId);
22127
- if (!entry) {
22128
- server.setActiveToolCall(null);
22129
- return { content: [{
22130
- type: "text",
22131
- text: `Document ${docId} not found`
22132
- }] };
22133
- }
22134
- server.setActiveToolCall(null);
22947
+ if (!entry) return { content: [{
22948
+ type: "text",
22949
+ text: `Document ${docId} not found`
22950
+ }] };
22135
22951
  return { content: [{
22136
22952
  type: "text",
22137
22953
  text: JSON.stringify({
@@ -22144,7 +22960,7 @@ function registerMetaTools(mcp, server) {
22144
22960
  });
22145
22961
  mcp.tool("update_metadata", "Update metadata fields on a document. Merges the provided fields into existing metadata.", {
22146
22962
  docId: zod.z.string().describe("Document ID."),
22147
- 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, 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. Geo/Map: geoType (\"marker\"|\"line\"|\"measure\"), geoLat, geoLng, geoDescription. Spatial 3D: spShape (\"box\"|\"sphere\"|\"cylinder\"|\"cone\"|\"plane\"|\"torus\"|\"glb\"), spX/spY/spZ, spRX/spRY/spRZ, spSX/spSY/spSZ, spColor, spOpacity (0-100). Dashboard: deskX, deskY, deskZ, deskMode (\"icon\"|\"widget-sm\"|\"widget-lg\"). Slides: slidesTransition (\"none\"|\"fade\"|\"slide\"), slidesTheme (\"dark\"|\"light\"). Chart: chartType (\"bar\"|\"stacked bar\"|\"line\"|\"donut\"|\"treemap\"), chartMetric, chartColorScheme, chartLimit, chartShowLegend, chartShowValues. Sheets: sheetsDefaultColWidth, sheetsDefaultRowHeight, sheetsShowGridlines, sheetsFreezeRows, sheetsFreezeCols. Cell formatting: bold, italic, textColor, bgColor, align, formula. Renderer config (on the page doc itself): kanbanColumnWidth, galleryColumns, galleryAspect, galleryCardStyle, galleryShowLabels, gallerySortBy, calendarView, calendarWeekStart, calendarShowWeekNumbers, tableMode, tableSortDir, checklistFilter, checklistSort, mapShowLabels, spatialGridVisible, showRefEdges, mediaRepeat, 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.")
22148
22964
  }, async ({ docId, meta }) => {
22149
22965
  server.setAutoStatus("writing", docId);
22150
22966
  server.setActiveToolCall({
@@ -22152,21 +22968,15 @@ function registerMetaTools(mcp, server) {
22152
22968
  target: docId
22153
22969
  });
22154
22970
  const treeMap = server.getTreeMap();
22155
- if (!treeMap) {
22156
- server.setActiveToolCall(null);
22157
- return { content: [{
22158
- type: "text",
22159
- text: "Not connected"
22160
- }] };
22161
- }
22971
+ if (!treeMap) return { content: [{
22972
+ type: "text",
22973
+ text: "Not connected"
22974
+ }] };
22162
22975
  const entry = treeMap.get(docId);
22163
- if (!entry) {
22164
- server.setActiveToolCall(null);
22165
- return { content: [{
22166
- type: "text",
22167
- text: `Document ${docId} not found`
22168
- }] };
22169
- }
22976
+ if (!entry) return { content: [{
22977
+ type: "text",
22978
+ text: `Document ${docId} not found`
22979
+ }] };
22170
22980
  treeMap.set(docId, {
22171
22981
  ...entry,
22172
22982
  meta: {
@@ -22175,7 +22985,6 @@ function registerMetaTools(mcp, server) {
22175
22985
  },
22176
22986
  updatedAt: Date.now()
22177
22987
  });
22178
- server.setActiveToolCall(null);
22179
22988
  return { content: [{
22180
22989
  type: "text",
22181
22990
  text: `Metadata updated for ${docId}`
@@ -22190,6 +22999,11 @@ function registerMetaTools(mcp, server) {
22190
22999
  */
22191
23000
  function registerFileTools(mcp, server) {
22192
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
+ });
22193
23007
  try {
22194
23008
  const uploads = await server.client.listUploads(docId);
22195
23009
  return { content: [{
@@ -22211,6 +23025,11 @@ function registerFileTools(mcp, server) {
22211
23025
  filePath: zod.z.string().describe("Absolute path to the local file to upload."),
22212
23026
  filename: zod.z.string().optional().describe("Override filename (defaults to basename of filePath).")
22213
23027
  }, async ({ docId, filePath, filename }) => {
23028
+ server.setAutoStatus("uploading", docId);
23029
+ server.setActiveToolCall({
23030
+ name: "upload_file",
23031
+ target: node_path.basename(filePath)
23032
+ });
22214
23033
  try {
22215
23034
  const resolvedPath = node_path.resolve(filePath);
22216
23035
  const data = node_fs.readFileSync(resolvedPath);
@@ -22236,6 +23055,11 @@ function registerFileTools(mcp, server) {
22236
23055
  uploadId: zod.z.string().describe("Upload ID to download."),
22237
23056
  saveTo: zod.z.string().describe("Absolute local file path to save the download.")
22238
23057
  }, async ({ docId, uploadId, saveTo }) => {
23058
+ server.setAutoStatus("reading", docId);
23059
+ server.setActiveToolCall({
23060
+ name: "download_file",
23061
+ target: node_path.basename(saveTo)
23062
+ });
22239
23063
  try {
22240
23064
  const blob = await server.client.getUpload(docId, uploadId);
22241
23065
  const buffer = Buffer.from(await blob.arrayBuffer());
@@ -22259,6 +23083,11 @@ function registerFileTools(mcp, server) {
22259
23083
  docId: zod.z.string().describe("Document ID."),
22260
23084
  uploadId: zod.z.string().describe("Upload ID to delete.")
22261
23085
  }, async ({ docId, uploadId }) => {
23086
+ server.setAutoStatus("writing", docId);
23087
+ server.setActiveToolCall({
23088
+ name: "delete_file",
23089
+ target: uploadId
23090
+ });
22262
23091
  try {
22263
23092
  await server.client.deleteUpload(docId, uploadId);
22264
23093
  return { content: [{
@@ -22300,6 +23129,10 @@ function registerAwarenessTools(mcp, server) {
22300
23129
  docId: zod.z.string().describe("Document ID to set awareness on."),
22301
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.")
22302
23131
  }, async ({ docId, fields }) => {
23132
+ server.setActiveToolCall({
23133
+ name: "set_doc_awareness",
23134
+ target: docId
23135
+ });
22303
23136
  try {
22304
23137
  const provider = await server.getChildProvider(docId);
22305
23138
  for (const [key, value] of Object.entries(fields)) provider.awareness.setLocalStateField(key, value ?? null);
@@ -22318,6 +23151,7 @@ function registerAwarenessTools(mcp, server) {
22318
23151
  }
22319
23152
  });
22320
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" });
22321
23155
  try {
22322
23156
  const treeMap = server.getTreeMap();
22323
23157
  const rootDocId = server.rootDocId;
@@ -22364,6 +23198,10 @@ function registerAwarenessTools(mcp, server) {
22364
23198
  }
22365
23199
  });
22366
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
+ });
22367
23205
  try {
22368
23206
  let awareness;
22369
23207
  if (docId) awareness = (await server.getChildProvider(docId)).awareness;
@@ -22408,16 +23246,13 @@ function registerChannelTools(mcp, server) {
22408
23246
  });
22409
23247
  const treeMap = server.getTreeMap();
22410
23248
  const rootDoc = server.rootDocument;
22411
- if (!treeMap || !rootDoc) {
22412
- server.setActiveToolCall(null);
22413
- return {
22414
- content: [{
22415
- type: "text",
22416
- text: "Not connected"
22417
- }],
22418
- isError: true
22419
- };
22420
- }
23249
+ if (!treeMap || !rootDoc) return {
23250
+ content: [{
23251
+ type: "text",
23252
+ text: "Not connected"
23253
+ }],
23254
+ isError: true
23255
+ };
22421
23256
  const label = `AI Reply — ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)}: ${text.slice(0, 40).replace(/\n/g, " ")}`;
22422
23257
  const replyId = crypto.randomUUID();
22423
23258
  const now = Date.now();
@@ -22433,7 +23268,6 @@ function registerChannelTools(mcp, server) {
22433
23268
  });
22434
23269
  populateYDocFromMarkdown((await server.getChildProvider(replyId)).document, text);
22435
23270
  if (task_id) server.clearAiTask(task_id);
22436
- server.setActiveToolCall(null);
22437
23271
  return { content: [{
22438
23272
  type: "text",
22439
23273
  text: JSON.stringify({
@@ -22442,7 +23276,6 @@ function registerChannelTools(mcp, server) {
22442
23276
  })
22443
23277
  }] };
22444
23278
  } catch (error) {
22445
- server.setActiveToolCall(null);
22446
23279
  return {
22447
23280
  content: [{
22448
23281
  type: "text",
@@ -22465,14 +23298,15 @@ function registerChannelTools(mcp, server) {
22465
23298
  }],
22466
23299
  isError: true
22467
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);
22468
23304
  rootProvider.sendStateless(JSON.stringify({
22469
23305
  type: "chat:send",
22470
23306
  channel,
22471
- content: text,
23307
+ content: normalized,
22472
23308
  sender_name: server.agentName
22473
23309
  }));
22474
- server.setAutoStatus(null);
22475
- server.setActiveToolCall(null);
22476
23310
  return { content: [{
22477
23311
  type: "text",
22478
23312
  text: `Sent to ${channel}`
@@ -22627,16 +23461,13 @@ function registerSvgTools(mcp, server) {
22627
23461
  target: docId
22628
23462
  });
22629
23463
  const cleanSvg = sanitizeSvg(svg);
22630
- if (!cleanSvg) {
22631
- server.setActiveToolCall(null);
22632
- return {
22633
- content: [{
22634
- type: "text",
22635
- text: "Error: SVG markup was empty or entirely stripped by sanitizer."
22636
- }],
22637
- isError: true
22638
- };
22639
- }
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
+ };
22640
23471
  const doc = (await server.getChildProvider(docId)).document;
22641
23472
  const fragment = doc.getXmlFragment("default");
22642
23473
  doc.transact(() => {
@@ -22647,13 +23478,11 @@ function registerSvgTools(mcp, server) {
22647
23478
  fragment.insert(insertPos, [el]);
22648
23479
  });
22649
23480
  server.setFocusedDoc(docId);
22650
- server.setActiveToolCall(null);
22651
23481
  return { content: [{
22652
23482
  type: "text",
22653
23483
  text: `SVG inserted into document ${docId}${title ? ` ("${title}")` : ""}`
22654
23484
  }] };
22655
23485
  } catch (error) {
22656
- server.setActiveToolCall(null);
22657
23486
  return {
22658
23487
  content: [{
22659
23488
  type: "text",
@@ -22711,24 +23540,38 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
22711
23540
 
22712
23541
  ## Page Types Reference
22713
23542
 
23543
+ ### Core Types (always available)
23544
+
22714
23545
  | Type | Children Are | Grandchildren Are | Depth | Key Meta on Children |
22715
23546
  |------|-------------|-------------------|-------|---------------------|
22716
23547
  | **doc** | Sub-documents | Sub-sub-documents | ∞ | — |
22717
23548
  | **kanban** | Columns | Cards | 2 | color, icon on cards |
22718
23549
  | **table** | Columns | Cells (positional rows) | 2 | — |
22719
- | **calendar** | Events | — | 1 | datetimeStart, datetimeEnd, allDay, color |
23550
+ | **calendar** | Events | — | 1 | datetimeStart, datetimeEnd, allDay, color, icon |
22720
23551
  | **timeline** | Epics | Tasks | 2 | dateStart, dateEnd, taskProgress, color |
22721
23552
  | **checklist** | Tasks | Sub-tasks | ∞ | checked, priority, dateEnd |
22722
23553
  | **outline** | Items | Sub-items | ∞ | — |
22723
- | **mindmap** | Central nodes | Branches | ∞ | mmX, mmY |
22724
23554
  | **graph** | Nodes | — | 1 | graphX, graphY, graphPinned, color |
22725
- | **gallery** | Items | — | 1 | geoLat, geoLng, datetimeStart, tags |
23555
+ | **gallery** | Items | — | 1 | geoLat, geoLng, datetimeStart, tags, rating, icon, color |
22726
23556
  | **map** | Markers/Lines | Points (for lines) | 2 | geoType, geoLat, geoLng, icon, color |
22727
- | **dashboard** | Items | | 1 | deskX, deskY, deskMode |
22728
- | **spatial** | Objects | Sub-parts | | spShape, spX/Y/Z, spColor, spOpacity |
22729
- | **media** | Tracks | | 1 | tags |
22730
- | **slides** | Slides | | 1 | |
22731
- | **whiteboard** | Objects | — | 1 | wbX, wbY, wbW, wbH |
23557
+ | **slides** | Slides | Sub-slides | 2 | slidesTransition, color |
23558
+ | **dashboard** | Items | | 1 | deskX, deskY, deskZ, deskMode |
23559
+ | **chart** | Data points | Data points | 2 | number (value), color, tags |
23560
+ | **sheets** | Columns | Cells | 2 | formula, bold, italic, textColor, bgColor, align |
23561
+ | **overview** | Pages | — | 1 | |
23562
+ | **call** | — (no children) | — | 0 | — |
23563
+
23564
+ Alias: \`desktop\` → \`dashboard\`.
23565
+
23566
+ ### Plugin Types (require plugin enabled on the server)
23567
+
23568
+ | Type | Plugin | Children Are | Grandchildren Are | Depth | Key Meta on Children |
23569
+ |------|--------|-------------|-------------------|-------|---------------------|
23570
+ | **spatial** | spatial | Objects | Sub-parts | ∞ | spShape, spX/Y/Z, spRX/RY/RZ, spSX/SY/SZ, color, spOpacity |
23571
+ | **media** | media | Tracks | — | ∞ | tags |
23572
+ | **coder** | coder | Files/Folders | — | ∞ | fileType, entry |
23573
+
23574
+ > Spatial uses the universal \`color\` key for object color — there is no \`spColor\`.
22732
23575
 
22733
23576
  ---
22734
23577
 
@@ -22801,18 +23644,65 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
22801
23644
  4. Modes: \`"icon"\` (small), \`"widget-sm"\` (240×180), \`"widget-lg"\` (400×320)
22802
23645
  5. Grid uses 80px cells
22803
23646
 
22804
- **Spatial (3D scene):**
23647
+ **Spatial (3D scene)** *(requires spatial plugin):*
22805
23648
  1. \`create_document(parentId, "3D Scene", "spatial")\`
22806
23649
  2. Create objects: \`create_document(sceneId, "Red Cube")\`
22807
- 3. Set 3D properties: \`update_metadata(objId, { spShape: "box", spColor: "#ef4444", spX: 0, spY: 1, spZ: 0, spSX: 2, spSY: 2, spSZ: 2 })\`
22808
- 4. Shapes: \`"box"\`, \`"sphere"\`, \`"cylinder"\`, \`"cone"\`, \`"plane"\`, \`"torus"\`, \`"glb"\` (uploaded 3D model)
23650
+ 3. Set 3D properties: \`update_metadata(objId, { spShape: "box", color: "#ef4444", spX: 0, spY: 1, spZ: 0, spSX: 2, spSY: 2, spSZ: 2 })\`
23651
+ 4. Shapes: \`"box"\`, \`"sphere"\`, \`"cylinder"\`, \`"cone"\`, \`"plane"\`, \`"torus"\`, \`"glb"\` (uploaded 3D model — set \`spModelUploadId\` and \`spModelDocId\`)
22809
23652
  5. Rotation (degrees): \`spRX\`, \`spRY\`, \`spRZ\`. Scale: \`spSX\`, \`spSY\`, \`spSZ\` (default 1). Opacity: \`spOpacity\` (0–100)
23653
+ 6. Use the universal \`color\` key — **never \`spColor\`**
22810
23654
 
22811
23655
  **Outline (nested items):**
22812
23656
  1. \`create_document(parentId, "Meeting Notes", "outline")\`
22813
23657
  2. Create items: \`create_document(outlineId, "Agenda Item 1")\`
22814
23658
  3. Create sub-items (unlimited depth): \`create_document(itemId, "Sub-point")\`
22815
23659
 
23660
+ **Slides (presentation with two-axis nav):**
23661
+ 1. \`create_document(parentId, "Q1 Review", "slides")\`
23662
+ 2. Create slides as direct children: \`create_document(deckId, "Intro")\`
23663
+ 3. Create sub-slides (vertical navigation) as grandchildren: \`create_document(slideId, "Deep dive")\`
23664
+ 4. Per-slide transition: \`update_metadata(slideId, { slidesTransition: "fade", color: "#6366f1" })\`
23665
+ 5. Deck-level theme: \`update_metadata(deckId, { slidesTheme: "dark" })\`
23666
+
23667
+ **Chart (data viz — manual or aggregation):**
23668
+ 1. \`create_document(parentId, "Sales", "chart")\`
23669
+ 2. Configure: \`update_metadata(chartId, { chartType: "bar", chartMetric: "value", chartShowLegend: true, chartLimit: 10 })\`
23670
+ 3. Modes:
23671
+ - **Manual data points**: create children with \`number\` (value) and optional \`tags\`/\`color\`
23672
+ \`create_document(chartId, "Q1")\` then \`update_metadata(dpId, { number: 42500, color: "#6366f1" })\`
23673
+ - **Aggregation**: set \`chartMetric\` to \`"type"\`/\`"tag"\`/\`"status"\`/\`"priority"\`/\`"activity"\`/\`"completion"\` and point the chart at a subtree — it aggregates the descendants' meta automatically
23674
+ 4. Chart types: \`"bar"\`, \`"stacked bar"\`, \`"line"\`, \`"donut"\`, \`"treemap"\`
23675
+ 5. Color schemes: \`"default"\`, \`"warm"\`, \`"cool"\`, \`"mono"\`
23676
+
23677
+ **Sheets (spreadsheet with formulas):**
23678
+ 1. \`create_document(parentId, "Budget", "sheets")\`
23679
+ 2. Create columns: \`create_document(sheetId, "A")\`, \`create_document(sheetId, "B")\`
23680
+ 3. Create cells under columns (positional rows, like \`table\`)
23681
+ 4. Formulas on cells: \`update_metadata(cellId, { formula: "=A1+B1" })\`
23682
+ 5. Cell formatting: \`update_metadata(cellId, { bold: true, bgColor: "#fef3c7", align: "right" })\`
23683
+ 6. Deck config: \`update_metadata(sheetId, { sheetsDefaultColWidth: 120, sheetsDefaultRowHeight: 28, sheetsShowGridlines: true, sheetsFreezeRows: 1, sheetsFreezeCols: 1 })\`
23684
+
23685
+ **Overview (space home):**
23686
+ 1. \`create_document(parentId, "Home", "overview")\`
23687
+ 2. No children required — renders activity, people, stats from the surrounding space
23688
+ 3. Children (if any) show as linked pages
23689
+
23690
+ **Call (video room):**
23691
+ 1. \`create_document(parentId, "Daily Standup", "call")\`
23692
+ 2. Video rooms have **no children** — do not add documents underneath
23693
+
23694
+ **Coder (multi-file collaborative editor)** *(requires coder plugin):*
23695
+ 1. \`create_document(parentId, "My App", "coder")\`
23696
+ 2. Create files/folders: \`create_document(projectId, "App.vue")\`, \`create_document(projectId, "src")\`
23697
+ 3. Set file type: \`update_metadata(fileId, { fileType: "vue", entry: true })\`
23698
+ 4. \`fileType\` options: \`"vue"\`, \`"ts"\`, \`"js"\`, \`"css"\`, \`"json"\`, \`"folder"\`
23699
+ 5. Mark the entry file with \`entry: true\` — the renderer uses it as the preview root
23700
+
23701
+ **Media (audio/video playlist)** *(requires media plugin):*
23702
+ 1. \`create_document(parentId, "Focus Mix", "media")\`
23703
+ 2. Create tracks: \`create_document(playlistId, "Track 1")\` — attach audio/video file via upload tool
23704
+ 3. Playlist config: \`update_metadata(playlistId, { mediaRepeat: "all", mediaShuffle: false })\`
23705
+
22816
23706
  ---
22817
23707
 
22818
23708
  ## Document References
@@ -22887,22 +23777,55 @@ In the **graph** page type, document references (embeds and links) create visibl
22887
23777
  |-----|------|-----------|--------|
22888
23778
  | \`kanbanColumnWidth\` | string | kanban | "narrow", "default", "wide" |
22889
23779
  | \`galleryColumns\` | number | gallery | 1–6 |
22890
- | \`galleryAspect\` | string | gallery | "square", "4:3", "16:9" |
23780
+ | \`galleryAspect\` | string | gallery | "square", "4:3", "3:2", "16:9", "free" |
23781
+ | \`galleryCardStyle\` | string | gallery | "default", "compact", "detailed" |
23782
+ | \`galleryShowLabels\` | boolean | gallery | show item labels |
23783
+ | \`gallerySortBy\` | string | gallery | "manual", "date", "name", "rating" |
22891
23784
  | \`calendarView\` | string | calendar | "month", "week", "day" |
22892
23785
  | \`calendarWeekStart\` | string | calendar | "sun", "mon" |
23786
+ | \`calendarShowWeekNumbers\` | boolean | calendar | — |
22893
23787
  | \`tableMode\` | string | table | "hierarchy", "flat" |
23788
+ | \`tableSortKey\` | string | table | meta key to sort by |
23789
+ | \`tableSortDir\` | string | table | "asc", "desc" |
23790
+ | \`timelineZoom\` | string | timeline | "week", "month", "quarter" |
23791
+ | \`timelinePixelsPerDay\` | number | timeline | zoom granularity |
23792
+ | \`timelineCenterDate\` | string | timeline | ISO date to center view |
23793
+ | \`checklistFilter\` | string | checklist | "all", "active", "completed" |
23794
+ | \`checklistSort\` | string | checklist | "manual", "priority", "due" |
23795
+ | \`mapShowLabels\` | boolean | map | — |
23796
+ | \`graphSpacing\` | string | graph | "compact", "default", "spacious" |
23797
+ | \`graphShowLabels\` | boolean | graph | — |
23798
+ | \`graphEdgeThickness\` | string | graph | "thin", "normal", "thick" |
22894
23799
  | \`showRefEdges\` | boolean | graph | show doc-reference edges |
22895
-
22896
- ### Spatial 3D Keys (for spatial children)
23800
+ | \`mmSpacing\` | string | mindmap-layout | spacing between branches |
23801
+ | \`spatialGridVisible\` | boolean | spatial | show ground grid |
23802
+ | \`slidesTheme\` | string | slides | "dark", "light" |
23803
+ | \`chartType\` | string | chart | "bar", "stacked bar", "line", "donut", "treemap" |
23804
+ | \`chartMetric\` | string | chart | "value", "type", "tag", "status", "priority", "activity", "completion" |
23805
+ | \`chartColorScheme\` | string | chart | "default", "warm", "cool", "mono" |
23806
+ | \`chartLimit\` | number | chart | 3–30 (max items) |
23807
+ | \`chartShowLegend\` | boolean | chart | — |
23808
+ | \`chartShowValues\` | boolean | chart | — |
23809
+ | \`sheetsDefaultColWidth\` | number | sheets | 40–500 |
23810
+ | \`sheetsDefaultRowHeight\` | number | sheets | 20–100 |
23811
+ | \`sheetsShowGridlines\` | boolean | sheets | — |
23812
+ | \`sheetsFreezeRows\` | number | sheets | frozen rows count |
23813
+ | \`sheetsFreezeCols\` | number | sheets | frozen cols count |
23814
+ | \`mediaRepeat\` | string | media | "off", "all", "one" (plugin) |
23815
+ | \`mediaShuffle\` | boolean | media | plugin |
23816
+
23817
+ ### Spatial 3D Keys (for spatial children — requires spatial plugin)
22897
23818
 
22898
23819
  | Key | Type | Default | Meaning |
22899
23820
  |-----|------|---------|---------|
22900
23821
  | \`spShape\` | string | — | "box", "sphere", "cylinder", "cone", "plane", "torus", "glb" |
22901
- | \`spColor\` | string | — | CSS color |
23822
+ | \`color\` | string | — | CSS color — universal key, **not** \`spColor\` |
22902
23823
  | \`spOpacity\` | number | 100 | 0–100 |
22903
23824
  | \`spX\`, \`spY\`, \`spZ\` | number | 0 | Position |
22904
23825
  | \`spRX\`, \`spRY\`, \`spRZ\` | number | 0 | Rotation (degrees) |
22905
23826
  | \`spSX\`, \`spSY\`, \`spSZ\` | number | 1 | Scale |
23827
+ | \`spModelUploadId\` | string | — | GLB upload ID (when \`spShape: "glb"\`) |
23828
+ | \`spModelDocId\` | string | — | Doc ID that owns the GLB upload |
22906
23829
 
22907
23830
  ### Dashboard Keys (for dashboard children)
22908
23831
 
@@ -22912,6 +23835,28 @@ In the **graph** page type, document references (embeds and links) create visibl
22912
23835
  | \`deskZ\` | number | Z-index (layering) |
22913
23836
  | \`deskMode\` | string | "icon", "widget-sm" (240×180), "widget-lg" (400×320) |
22914
23837
 
23838
+ ### Sheets Cell Formatting (per-cell meta)
23839
+
23840
+ | Key | Type | Meaning |
23841
+ |-----|------|---------|
23842
+ | \`formula\` | string | Cell formula (e.g. "=A1+B1") |
23843
+ | \`bold\` | boolean | — |
23844
+ | \`italic\` | boolean | — |
23845
+ | \`textColor\` | string | CSS color |
23846
+ | \`bgColor\` | string | CSS color |
23847
+ | \`align\` | string | "left", "center", "right" |
23848
+
23849
+ ### Coder Keys (for coder children — plugin)
23850
+
23851
+ | Key | Type | Meaning |
23852
+ |-----|------|---------|
23853
+ | \`fileType\` | string | "vue", "ts", "js", "css", "json", "folder" |
23854
+ | \`entry\` | boolean | Mark this file as the preview entry point |
23855
+
23856
+ ### Discovering What Metadata Applies
23857
+
23858
+ Use the \`list_page_types\` tool to enumerate all known page types and their declared \`metaSchema\` (fields that apply to children/descendants) and \`defaultMetaFields\` (renderer config fields on the page itself). It's the authoritative list of what meta keys make sense for any given page type.
23859
+
22915
23860
  ---
22916
23861
 
22917
23862
  ## Content Structure
@@ -22994,8 +23939,11 @@ Always clear fields when done by setting them to \`null\`.
22994
23939
  | **Outline** | \`outline:editing\` | nodeId | Editing an outline node |
22995
23940
  | **Gallery** | \`gallery:focused\` | itemId | Item hovered/selected |
22996
23941
  | **Timeline** | \`timeline:focused\` | taskId | Task selected |
22997
- | **Mindmap** | \`mindmap:focused\` | nodeId | Node selected/edited |
22998
23942
  | **Graph** | \`graph:focused\` | nodeId | Node hovered/selected |
23943
+ | **Slides** | \`slides:current\` | slideId | Slide being presented |
23944
+ | **Spatial** | \`spatial:selected\`, \`spatial:camera\` | objectId / camera state | plugin |
23945
+ | **Media** | \`media:playing\`, \`media:position\` | trackId / 0–1 | plugin |
23946
+ | **Coder** | \`coder:activeFile\` | fileId | plugin |
22999
23947
  | **Map** | \`map:focused\` | markerId | Marker hovered/selected |
23000
23948
  | **Doc** | \`doc:scroll\` | 0–1 number | Scroll position in document |
23001
23949
 
@@ -23338,6 +24286,9 @@ var HookBridge = class {
23338
24286
  }
23339
24287
  routeEvent(payload) {
23340
24288
  switch (payload.hook_event_name) {
24289
+ case "UserPromptSubmit":
24290
+ this.onUserPromptSubmit();
24291
+ break;
23341
24292
  case "PreToolUse":
23342
24293
  this.onPreToolUse(payload);
23343
24294
  break;
@@ -23348,13 +24299,18 @@ var HookBridge = class {
23348
24299
  this.onSubagentStart(payload);
23349
24300
  break;
23350
24301
  case "SubagentStop":
23351
- this.onSubagentStop(payload);
24302
+ this.onSubagentStop();
23352
24303
  break;
23353
24304
  case "Stop":
23354
24305
  this.onStop();
23355
24306
  break;
23356
24307
  }
23357
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
+ }
23358
24314
  onPreToolUse(payload) {
23359
24315
  const toolName = payload.tool_name ?? "";
23360
24316
  if (toolName.startsWith("mcp__abracadabra__")) return;
@@ -23366,7 +24322,6 @@ var HookBridge = class {
23366
24322
  }
23367
24323
  onPostToolUse(payload) {
23368
24324
  if ((payload.tool_name ?? "").startsWith("mcp__abracadabra__")) return;
23369
- this.server.setAutoStatus("thinking");
23370
24325
  }
23371
24326
  onSubagentStart(payload) {
23372
24327
  const agentType = payload.agent_type ?? "agent";
@@ -23376,9 +24331,7 @@ var HookBridge = class {
23376
24331
  });
23377
24332
  this.server.setAutoStatus("thinking");
23378
24333
  }
23379
- onSubagentStop(_payload) {
23380
- this.server.setAutoStatus("thinking");
23381
- }
24334
+ onSubagentStop() {}
23382
24335
  onStop() {
23383
24336
  this.server.setAutoStatus(null);
23384
24337
  this.server.setActiveToolCall(null);
@@ -23391,11 +24344,19 @@ var HookBridge = class {
23391
24344
  * Abracadabra MCP Server — entry point.
23392
24345
  *
23393
24346
  * Environment variables:
23394
- * ABRA_URL (required) — Server URL (e.g. http://localhost:1234)
24347
+ * ABRA_URL (required) — Server URL (e.g. http://localhost:1234)
23395
24348
  * ABRA_AGENT_NAME — Display name (default: "AI Assistant")
23396
24349
  * ABRA_AGENT_COLOR — HSL color for presence (default: "hsl(270, 80%, 60%)")
23397
24350
  * ABRA_INVITE_CODE — Invite code for first-run registration (grants role)
23398
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])
23399
24360
  */
23400
24361
  async function main() {
23401
24362
  const url = process.env.ABRA_URL;
@@ -23403,13 +24364,27 @@ async function main() {
23403
24364
  console.error("Missing required environment variable: ABRA_URL");
23404
24365
  process.exit(1);
23405
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;
23406
24378
  const server = new AbracadabraMCPServer({
23407
24379
  url,
23408
24380
  agentName: process.env.ABRA_AGENT_NAME,
23409
24381
  agentColor: process.env.ABRA_AGENT_COLOR,
23410
24382
  inviteCode: process.env.ABRA_INVITE_CODE,
23411
- keyFile: process.env.ABRA_KEY_FILE
24383
+ keyFile: process.env.ABRA_KEY_FILE,
24384
+ triggerMode,
24385
+ mentionAliases
23412
24386
  });
24387
+ console.error(`[abracadabra-mcp] Trigger mode: ${triggerMode}; aliases: ${server.mentionAliases.join(", ")}`);
23413
24388
  const mcp = new McpServer({
23414
24389
  name: "abracadabra",
23415
24390
  version: "1.0.0"