@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.
@@ -3757,7 +3757,7 @@ const propertyKeyTypes = new Set([
3757
3757
  "number",
3758
3758
  "symbol"
3759
3759
  ]);
3760
- function escapeRegex(str) {
3760
+ function escapeRegex$1(str) {
3761
3761
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3762
3762
  }
3763
3763
  function clone(inst, def, params) {
@@ -4468,7 +4468,7 @@ const $ZodCheckUpperCase = /* @__PURE__ */ $constructor("$ZodCheckUpperCase", (i
4468
4468
  });
4469
4469
  const $ZodCheckIncludes = /* @__PURE__ */ $constructor("$ZodCheckIncludes", (inst, def) => {
4470
4470
  $ZodCheck.init(inst, def);
4471
- const escapedRegex = escapeRegex(def.includes);
4471
+ const escapedRegex = escapeRegex$1(def.includes);
4472
4472
  const pattern = new RegExp(typeof def.position === "number" ? `^.{${def.position}}${escapedRegex}` : escapedRegex);
4473
4473
  def.pattern = pattern;
4474
4474
  inst._zod.onattach.push((inst) => {
@@ -4491,7 +4491,7 @@ const $ZodCheckIncludes = /* @__PURE__ */ $constructor("$ZodCheckIncludes", (ins
4491
4491
  });
4492
4492
  const $ZodCheckStartsWith = /* @__PURE__ */ $constructor("$ZodCheckStartsWith", (inst, def) => {
4493
4493
  $ZodCheck.init(inst, def);
4494
- const pattern = new RegExp(`^${escapeRegex(def.prefix)}.*`);
4494
+ const pattern = new RegExp(`^${escapeRegex$1(def.prefix)}.*`);
4495
4495
  def.pattern ?? (def.pattern = pattern);
4496
4496
  inst._zod.onattach.push((inst) => {
4497
4497
  const bag = inst._zod.bag;
@@ -4513,7 +4513,7 @@ const $ZodCheckStartsWith = /* @__PURE__ */ $constructor("$ZodCheckStartsWith",
4513
4513
  });
4514
4514
  const $ZodCheckEndsWith = /* @__PURE__ */ $constructor("$ZodCheckEndsWith", (inst, def) => {
4515
4515
  $ZodCheck.init(inst, def);
4516
- const pattern = new RegExp(`.*${escapeRegex(def.suffix)}$`);
4516
+ const pattern = new RegExp(`.*${escapeRegex$1(def.suffix)}$`);
4517
4517
  def.pattern ?? (def.pattern = pattern);
4518
4518
  inst._zod.onattach.push((inst) => {
4519
4519
  const bag = inst._zod.bag;
@@ -5545,7 +5545,7 @@ const $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
5545
5545
  const values = getEnumValues(def.entries);
5546
5546
  const valuesSet = new Set(values);
5547
5547
  inst._zod.values = valuesSet;
5548
- inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex(o) : o.toString()).join("|")})$`);
5548
+ inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex$1(o) : o.toString()).join("|")})$`);
5549
5549
  inst._zod.parse = (payload, _ctx) => {
5550
5550
  const input = payload.value;
5551
5551
  if (valuesSet.has(input)) return payload;
@@ -5563,7 +5563,7 @@ const $ZodLiteral = /* @__PURE__ */ $constructor("$ZodLiteral", (inst, def) => {
5563
5563
  if (def.values.length === 0) throw new Error("Cannot create literal schema with no valid values");
5564
5564
  const values = new Set(def.values);
5565
5565
  inst._zod.values = values;
5566
- inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex(o) : o ? escapeRegex(o.toString()) : String(o)).join("|")})$`);
5566
+ inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex$1(o) : o ? escapeRegex$1(o.toString()) : String(o)).join("|")})$`);
5567
5567
  inst._zod.parse = (payload, _ctx) => {
5568
5568
  const input = payload.value;
5569
5569
  if (values.has(input)) return payload;
@@ -19965,6 +19965,47 @@ function signChallenge(challengeB64, privateKey) {
19965
19965
  return toBase64url(sign(challenge, privateKey));
19966
19966
  }
19967
19967
 
19968
+ //#endregion
19969
+ //#region packages/mcp/src/mentions.ts
19970
+ /**
19971
+ * Mention parsing for chat messages.
19972
+ *
19973
+ * Recognizes `@alias` tokens (case-insensitive, word-boundary) so the agent
19974
+ * can decide whether a group-chat message is directed at it.
19975
+ */
19976
+ /** Escape regex metacharacters in an alias string. */
19977
+ function escapeRegex(s) {
19978
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
19979
+ }
19980
+ /**
19981
+ * Build a regex that matches `@<alias>` for any of the given aliases.
19982
+ * Requires a non-word char (or start) before `@` and a word boundary after the alias
19983
+ * so `@Claude` matches but `email@claudesomething` does not.
19984
+ */
19985
+ function buildMentionRegex(aliases) {
19986
+ const cleaned = aliases.map((a) => a.trim()).filter((a) => a.length > 0);
19987
+ if (cleaned.length === 0) return null;
19988
+ const alt = cleaned.map(escapeRegex).join("|");
19989
+ return new RegExp(`(?:^|[^\\w@])@(?:${alt})\\b`, "i");
19990
+ }
19991
+ /** Returns true if `text` contains `@alias` for any alias (case-insensitive). */
19992
+ function containsMention(text, aliases) {
19993
+ const re = buildMentionRegex(aliases);
19994
+ if (!re) return false;
19995
+ return re.test(text);
19996
+ }
19997
+ /**
19998
+ * Remove `@alias` tokens from the text. Leaves surrounding whitespace tidy so
19999
+ * the cleaned prompt reads naturally (e.g. `"@Claude help"` → `"help"`).
20000
+ */
20001
+ function stripMention(text, aliases) {
20002
+ const cleaned = aliases.map((a) => a.trim()).filter((a) => a.length > 0);
20003
+ if (cleaned.length === 0) return text;
20004
+ const alt = cleaned.map(escapeRegex).join("|");
20005
+ const re = new RegExp(`(^|\\s)@(?:${alt})\\b[,:]?\\s*`, "gi");
20006
+ return text.replace(re, (_m, lead) => lead ? " " : "").replace(/\s{2,}/g, " ").trim();
20007
+ }
20008
+
19968
20009
  //#endregion
19969
20010
  //#region packages/mcp/src/server.ts
19970
20011
  /**
@@ -19975,7 +20016,10 @@ function signChallenge(challengeB64, privateKey) {
19975
20016
  * Use switchSpace(docId) to change the active space.
19976
20017
  */
19977
20018
  const IDLE_TIMEOUT_MS = 300 * 1e3;
19978
- var AbracadabraMCPServer = class {
20019
+ var AbracadabraMCPServer = class AbracadabraMCPServer {
20020
+ static {
20021
+ this.TOOL_HISTORY_MAX = 20;
20022
+ }
19979
20023
  constructor(config) {
19980
20024
  this._serverInfo = null;
19981
20025
  this._rootDocId = null;
@@ -19992,6 +20036,7 @@ var AbracadabraMCPServer = class {
19992
20036
  this._typingInterval = null;
19993
20037
  this._lastChatChannel = null;
19994
20038
  this._signFn = null;
20039
+ this._toolHistory = [];
19995
20040
  this.config = config;
19996
20041
  this.client = new AbracadabraClient({
19997
20042
  url: config.url,
@@ -20004,6 +20049,14 @@ var AbracadabraMCPServer = class {
20004
20049
  get agentColor() {
20005
20050
  return this.config.agentColor || "hsl(270, 80%, 60%)";
20006
20051
  }
20052
+ get triggerMode() {
20053
+ return this.config.triggerMode ?? "mention+task";
20054
+ }
20055
+ get mentionAliases() {
20056
+ const explicit = this.config.mentionAliases?.filter((a) => a.trim().length > 0);
20057
+ if (explicit && explicit.length > 0) return explicit;
20058
+ return [this.agentName];
20059
+ }
20007
20060
  get serverInfo() {
20008
20061
  return this._serverInfo;
20009
20062
  }
@@ -20044,7 +20097,7 @@ var AbracadabraMCPServer = class {
20044
20097
  await this.client.loginWithKey(keypair.publicKeyB64, signFn);
20045
20098
  } else throw err;
20046
20099
  }
20047
- console.error(`[abracadabra-mcp] Authenticated as ${this.agentName} (${keypair.publicKeyB64.slice(0, 12)}...)`);
20100
+ console.error(`[abracadabra-mcp] Authenticated as ${this.agentName} (pubkey=${keypair.publicKeyB64})`);
20048
20101
  this._serverInfo = await this.client.serverInfo();
20049
20102
  let initialDocId = this._serverInfo.index_doc_id ?? null;
20050
20103
  try {
@@ -20111,6 +20164,8 @@ var AbracadabraMCPServer = class {
20111
20164
  provider.awareness.setLocalStateField("status", null);
20112
20165
  provider.awareness.setLocalStateField("activeToolCall", null);
20113
20166
  provider.awareness.setLocalStateField("statusContext", null);
20167
+ provider.awareness.setLocalStateField("turnId", null);
20168
+ provider.awareness.setLocalStateField("toolHistory", []);
20114
20169
  const conn = {
20115
20170
  doc,
20116
20171
  provider,
@@ -20145,6 +20200,17 @@ var AbracadabraMCPServer = class {
20145
20200
  getTrashMap() {
20146
20201
  return this._activeConnection?.doc.getMap("doc-trash") ?? null;
20147
20202
  }
20203
+ /** Get plugin names enabled in the active space via space-plugins Y.Map. */
20204
+ getEnabledPluginNames() {
20205
+ const doc = this._activeConnection?.doc;
20206
+ if (!doc) return [];
20207
+ const pluginsMap = doc.getMap("space-plugins");
20208
+ const names = [];
20209
+ pluginsMap.forEach((value, key) => {
20210
+ if ((value?.toJSON ? value.toJSON() : value)?.enabled) names.push(key);
20211
+ });
20212
+ return names;
20213
+ }
20148
20214
  /**
20149
20215
  * Get or create a child provider for a given document ID.
20150
20216
  * Caches providers and waits for sync before returning.
@@ -20223,6 +20289,7 @@ var AbracadabraMCPServer = class {
20223
20289
  _observeRootAwareness(provider) {
20224
20290
  const selfId = provider.awareness.clientID;
20225
20291
  provider.awareness.on("change", () => {
20292
+ if (this.triggerMode === "mention") return;
20226
20293
  const states = provider.awareness.getStates();
20227
20294
  for (const [clientId, state] of states) {
20228
20295
  if (clientId === selfId) continue;
@@ -20235,6 +20302,7 @@ var AbracadabraMCPServer = class {
20235
20302
  const user = state["user"];
20236
20303
  const senderName = user && typeof user === "object" && typeof user.name === "string" ? user.name : "Unknown";
20237
20304
  console.error(`[abracadabra-mcp] Handling ai:task id=${id} from ${senderName}: ${text.slice(0, 80)}`);
20305
+ this._beginTurn();
20238
20306
  this.setAutoStatus("thinking");
20239
20307
  this._dispatchAiTask({
20240
20308
  id,
@@ -20297,9 +20365,28 @@ var AbracadabraMCPServer = class {
20297
20365
  if (data.sender_id && data.sender_id === this._userId) return;
20298
20366
  const channel = data.channel;
20299
20367
  const docId = channel?.startsWith("group:") ? channel.slice(6) : "";
20300
- if (channel?.startsWith("dm:")) {
20368
+ const isDM = channel?.startsWith("dm:") ?? false;
20369
+ const isGroup = channel?.startsWith("group:") ?? false;
20370
+ if (isDM) {
20301
20371
  const parts = channel.split(":");
20302
- if (parts.length === 3 && parts[1] !== this._userId && parts[2] !== this._userId) return;
20372
+ if (parts.length === 3 && parts[1] !== this._userId && parts[2] !== this._userId) {
20373
+ 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.`);
20374
+ return;
20375
+ }
20376
+ }
20377
+ const mode = this.triggerMode;
20378
+ const content = typeof data.content === "string" ? data.content : "";
20379
+ let dispatchContent = content;
20380
+ if (isGroup) {
20381
+ if (mode === "task") return;
20382
+ if (mode === "mention" || mode === "mention+task") {
20383
+ const aliases = this.mentionAliases;
20384
+ if (!containsMention(content, aliases)) {
20385
+ console.error(`[abracadabra-mcp] skipped message on ${channel} — no @mention for ${aliases.join("|")}`);
20386
+ return;
20387
+ }
20388
+ dispatchContent = stripMention(content, aliases) || content;
20389
+ }
20303
20390
  }
20304
20391
  if (channel) {
20305
20392
  const rootProvider = this._activeConnection?.provider;
@@ -20309,14 +20396,13 @@ var AbracadabraMCPServer = class {
20309
20396
  timestamp: Math.floor(Date.now() / 1e3)
20310
20397
  }));
20311
20398
  this._lastChatChannel = channel;
20312
- this.sendTypingIndicator(channel);
20313
- this._startTypingInterval(channel);
20314
20399
  }
20400
+ this._beginTurn();
20315
20401
  this.setAutoStatus("thinking");
20316
20402
  await this._serverRef.notification({
20317
20403
  method: "notifications/claude/channel",
20318
20404
  params: {
20319
- content: data.content ?? "",
20405
+ content: dispatchContent,
20320
20406
  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.`,
20321
20407
  meta: {
20322
20408
  source: "abracadabra",
@@ -20347,13 +20433,35 @@ var AbracadabraMCPServer = class {
20347
20433
  if (docId !== void 0) provider.awareness.setLocalStateField("docId", docId);
20348
20434
  const context = status ? statusContext !== void 0 ? statusContext : this._lastChatChannel : null;
20349
20435
  provider.awareness.setLocalStateField("statusContext", context ?? null);
20350
- if (!status) this._stopTypingInterval();
20436
+ if (!status) {
20437
+ this._stopTypingInterval();
20438
+ provider.awareness.setLocalStateField("activeToolCall", null);
20439
+ provider.awareness.setLocalStateField("turnId", null);
20440
+ this._toolHistory = [];
20441
+ provider.awareness.setLocalStateField("toolHistory", []);
20442
+ }
20351
20443
  if (status) this._statusClearTimer = setTimeout(() => {
20352
20444
  provider.awareness.setLocalStateField("status", null);
20353
20445
  provider.awareness.setLocalStateField("activeToolCall", null);
20354
20446
  provider.awareness.setLocalStateField("statusContext", null);
20447
+ provider.awareness.setLocalStateField("turnId", null);
20448
+ this._toolHistory = [];
20449
+ provider.awareness.setLocalStateField("toolHistory", []);
20355
20450
  this._stopTypingInterval();
20356
- }, 3e4);
20451
+ }, 1e4);
20452
+ }
20453
+ /**
20454
+ * Start a new agent turn. Mints a fresh UUID and writes it to awareness so
20455
+ * the dashboard can gate the incantation on "there is an active turn",
20456
+ * decoupled from the (racier) status field. Called from chat arrival and
20457
+ * ai:task dispatch right before `setAutoStatus('thinking')`.
20458
+ */
20459
+ _beginTurn() {
20460
+ const provider = this._activeConnection?.provider;
20461
+ if (!provider) return;
20462
+ this._toolHistory = [];
20463
+ provider.awareness.setLocalStateField("toolHistory", []);
20464
+ provider.awareness.setLocalStateField("turnId", crypto.randomUUID());
20357
20465
  }
20358
20466
  /** Re-send typing indicator every 2s so dashboard keeps showing it (expires at 3s). */
20359
20467
  _startTypingInterval(channel) {
@@ -20371,10 +20479,28 @@ var AbracadabraMCPServer = class {
20371
20479
  }
20372
20480
  /**
20373
20481
  * Broadcast which tool the agent is currently executing.
20374
- * Dashboard renders this as a ChatTool indicator.
20482
+ *
20483
+ * Renders as a ChatTool pill on the dashboard. On non-null calls, the tool
20484
+ * is also appended to `toolHistory` (capped at TOOL_HISTORY_MAX) and written
20485
+ * to awareness so the dashboard's inline trace can show the turn's recent
20486
+ * activity. Tools do NOT clear (`setActiveToolCall(null)`) on completion —
20487
+ * the pill stays until the next tool replaces it or `setAutoStatus(null)`
20488
+ * flushes the turn. This keeps pills visible long enough to see.
20375
20489
  */
20376
20490
  setActiveToolCall(toolCall) {
20377
- this._activeConnection?.provider?.awareness.setLocalStateField("activeToolCall", toolCall);
20491
+ const provider = this._activeConnection?.provider;
20492
+ if (!provider) return;
20493
+ provider.awareness.setLocalStateField("activeToolCall", toolCall);
20494
+ if (toolCall) {
20495
+ this._toolHistory.push({
20496
+ tool: toolCall.name,
20497
+ target: toolCall.target,
20498
+ ts: Date.now(),
20499
+ channel: this._lastChatChannel
20500
+ });
20501
+ if (this._toolHistory.length > AbracadabraMCPServer.TOOL_HISTORY_MAX) this._toolHistory.splice(0, this._toolHistory.length - AbracadabraMCPServer.TOOL_HISTORY_MAX);
20502
+ provider.awareness.setLocalStateField("toolHistory", [...this._toolHistory]);
20503
+ }
20378
20504
  }
20379
20505
  /**
20380
20506
  * Send a typing indicator to a chat channel.
@@ -20405,8 +20531,11 @@ var AbracadabraMCPServer = class {
20405
20531
  conn.provider.awareness.setLocalStateField("status", null);
20406
20532
  conn.provider.awareness.setLocalStateField("activeToolCall", null);
20407
20533
  conn.provider.awareness.setLocalStateField("statusContext", null);
20534
+ conn.provider.awareness.setLocalStateField("turnId", null);
20535
+ conn.provider.awareness.setLocalStateField("toolHistory", []);
20408
20536
  conn.provider.destroy();
20409
20537
  }
20538
+ this._toolHistory = [];
20410
20539
  this._spaceConnections.clear();
20411
20540
  this._activeConnection = null;
20412
20541
  console.error("[abracadabra-mcp] Shutdown complete");
@@ -20431,6 +20560,724 @@ function docToSpaceMeta(doc) {
20431
20560
  };
20432
20561
  }
20433
20562
 
20563
+ //#endregion
20564
+ //#region packages/mcp/src/converters/page-types.ts
20565
+ const PAGE_TYPES = {
20566
+ doc: {
20567
+ key: "doc",
20568
+ label: "Document",
20569
+ icon: "file-text",
20570
+ description: "Rich text document with real-time collaboration",
20571
+ core: true,
20572
+ supportsChildren: true
20573
+ },
20574
+ kanban: {
20575
+ key: "kanban",
20576
+ label: "Kanban",
20577
+ icon: "kanban",
20578
+ description: "Drag-and-drop task board with columns and cards",
20579
+ core: true,
20580
+ supportsChildren: true,
20581
+ childLabel: "Column",
20582
+ grandchildLabel: "Card",
20583
+ defaultMetaFields: [{
20584
+ type: "select",
20585
+ key: "kanbanColumnWidth",
20586
+ options: [
20587
+ "narrow",
20588
+ "default",
20589
+ "wide"
20590
+ ],
20591
+ label: "Column Width"
20592
+ }]
20593
+ },
20594
+ gallery: {
20595
+ key: "gallery",
20596
+ label: "Gallery",
20597
+ icon: "images",
20598
+ description: "Visual grid of items with rich content",
20599
+ core: true,
20600
+ supportsChildren: true,
20601
+ childLabel: "Item",
20602
+ metaSchema: [
20603
+ {
20604
+ type: "location",
20605
+ latKey: "geoLat",
20606
+ lngKey: "geoLng",
20607
+ label: "Location"
20608
+ },
20609
+ {
20610
+ type: "datetime",
20611
+ key: "datetimeStart",
20612
+ label: "Date"
20613
+ },
20614
+ {
20615
+ type: "tags",
20616
+ key: "tags",
20617
+ label: "Tags"
20618
+ },
20619
+ {
20620
+ type: "rating",
20621
+ key: "rating",
20622
+ max: 5,
20623
+ label: "Rating"
20624
+ },
20625
+ {
20626
+ type: "icon",
20627
+ key: "icon",
20628
+ label: "Icon"
20629
+ },
20630
+ {
20631
+ type: "colorPreset",
20632
+ key: "color",
20633
+ presets: [
20634
+ "#6366f1",
20635
+ "#ec4899",
20636
+ "#f97316",
20637
+ "#22c55e",
20638
+ "#3b82f6",
20639
+ "#a855f7"
20640
+ ],
20641
+ label: "Color"
20642
+ }
20643
+ ],
20644
+ defaultMetaFields: [
20645
+ {
20646
+ type: "number",
20647
+ key: "galleryColumns",
20648
+ min: 1,
20649
+ max: 6,
20650
+ step: 1,
20651
+ label: "Columns"
20652
+ },
20653
+ {
20654
+ type: "select",
20655
+ key: "galleryAspect",
20656
+ options: [
20657
+ "square",
20658
+ "4:3",
20659
+ "3:2",
20660
+ "16:9",
20661
+ "free"
20662
+ ],
20663
+ label: "Aspect Ratio"
20664
+ },
20665
+ {
20666
+ type: "select",
20667
+ key: "galleryCardStyle",
20668
+ options: [
20669
+ "default",
20670
+ "compact",
20671
+ "detailed"
20672
+ ],
20673
+ label: "Card Style"
20674
+ },
20675
+ {
20676
+ type: "toggle",
20677
+ key: "galleryShowLabels",
20678
+ label: "Show Labels"
20679
+ },
20680
+ {
20681
+ type: "select",
20682
+ key: "gallerySortBy",
20683
+ options: [
20684
+ "manual",
20685
+ "date",
20686
+ "name",
20687
+ "rating"
20688
+ ],
20689
+ label: "Sort"
20690
+ }
20691
+ ]
20692
+ },
20693
+ table: {
20694
+ key: "table",
20695
+ label: "Table",
20696
+ icon: "table",
20697
+ description: "Collaborative spreadsheet with custom fields",
20698
+ core: true,
20699
+ supportsChildren: true,
20700
+ childLabel: "Column",
20701
+ grandchildLabel: "Row",
20702
+ defaultMetaFields: [{
20703
+ type: "select",
20704
+ key: "tableMode",
20705
+ options: ["hierarchy", "flat"],
20706
+ label: "Mode"
20707
+ }, {
20708
+ type: "select",
20709
+ key: "tableSortDir",
20710
+ options: ["asc", "desc"],
20711
+ label: "Sort Direction"
20712
+ }]
20713
+ },
20714
+ outline: {
20715
+ key: "outline",
20716
+ label: "Outline",
20717
+ icon: "list-tree",
20718
+ description: "Hierarchical outline with keyboard navigation",
20719
+ core: true,
20720
+ supportsChildren: true,
20721
+ childLabel: "Item",
20722
+ defaultDepth: -1
20723
+ },
20724
+ checklist: {
20725
+ key: "checklist",
20726
+ label: "Checklist",
20727
+ icon: "check-square",
20728
+ description: "Collaborative checklist with sub-tasks, drag-and-drop, and due dates",
20729
+ core: true,
20730
+ supportsChildren: true,
20731
+ childLabel: "Task",
20732
+ defaultDepth: -1,
20733
+ metaSchema: [
20734
+ {
20735
+ type: "toggle",
20736
+ key: "checked",
20737
+ label: "Done"
20738
+ },
20739
+ {
20740
+ type: "select",
20741
+ key: "priority",
20742
+ options: [
20743
+ "none",
20744
+ "low",
20745
+ "medium",
20746
+ "high"
20747
+ ],
20748
+ label: "Priority"
20749
+ },
20750
+ {
20751
+ type: "date",
20752
+ key: "dateEnd",
20753
+ label: "Due date"
20754
+ }
20755
+ ],
20756
+ defaultMetaFields: [{
20757
+ type: "select",
20758
+ key: "checklistFilter",
20759
+ options: [
20760
+ "all",
20761
+ "active",
20762
+ "completed"
20763
+ ],
20764
+ label: "Filter"
20765
+ }, {
20766
+ type: "select",
20767
+ key: "checklistSort",
20768
+ options: [
20769
+ "manual",
20770
+ "priority",
20771
+ "due"
20772
+ ],
20773
+ label: "Sort"
20774
+ }]
20775
+ },
20776
+ graph: {
20777
+ key: "graph",
20778
+ label: "Graph",
20779
+ icon: "git-fork",
20780
+ description: "Force-directed knowledge graph — full document tree as nodes & edges",
20781
+ core: true,
20782
+ supportsChildren: true,
20783
+ childLabel: "Node",
20784
+ defaultMetaFields: [{
20785
+ type: "toggle",
20786
+ key: "showRefEdges",
20787
+ label: "Show Ref Edges",
20788
+ default: true
20789
+ }]
20790
+ },
20791
+ timeline: {
20792
+ key: "timeline",
20793
+ label: "Timeline",
20794
+ icon: "gantt-chart",
20795
+ description: "Gantt-style project timeline with epics and tasks",
20796
+ core: true,
20797
+ supportsChildren: true,
20798
+ childLabel: "Epic",
20799
+ grandchildLabel: "Task",
20800
+ metaSchema: [
20801
+ {
20802
+ type: "daterange",
20803
+ startKey: "dateStart",
20804
+ endKey: "dateEnd"
20805
+ },
20806
+ {
20807
+ type: "slider",
20808
+ key: "taskProgress",
20809
+ min: 0,
20810
+ max: 100,
20811
+ label: "Progress"
20812
+ },
20813
+ {
20814
+ type: "colorPreset",
20815
+ key: "color",
20816
+ presets: [
20817
+ "#6366f1",
20818
+ "#818cf8",
20819
+ "#f97316",
20820
+ "#22c55e",
20821
+ "#3b82f6",
20822
+ "#a855f7"
20823
+ ],
20824
+ label: "Color"
20825
+ }
20826
+ ]
20827
+ },
20828
+ calendar: {
20829
+ key: "calendar",
20830
+ label: "Calendar",
20831
+ icon: "calendar",
20832
+ description: "Event calendar with month, week, and day views",
20833
+ core: true,
20834
+ supportsChildren: true,
20835
+ childLabel: "Event",
20836
+ metaSchema: [
20837
+ {
20838
+ type: "datetimerange",
20839
+ startKey: "datetimeStart",
20840
+ endKey: "datetimeEnd",
20841
+ allDayKey: "allDay"
20842
+ },
20843
+ {
20844
+ type: "colorPreset",
20845
+ key: "color",
20846
+ presets: [
20847
+ "#6366f1",
20848
+ "#ec4899",
20849
+ "#f97316",
20850
+ "#22c55e",
20851
+ "#3b82f6",
20852
+ "#a855f7"
20853
+ ],
20854
+ label: "Color"
20855
+ },
20856
+ {
20857
+ type: "icon",
20858
+ key: "icon",
20859
+ label: "Icon"
20860
+ }
20861
+ ],
20862
+ defaultMetaFields: [
20863
+ {
20864
+ type: "select",
20865
+ key: "calendarWeekStart",
20866
+ options: ["sun", "mon"],
20867
+ label: "Week Starts"
20868
+ },
20869
+ {
20870
+ type: "select",
20871
+ key: "calendarView",
20872
+ options: [
20873
+ "month",
20874
+ "week",
20875
+ "day"
20876
+ ],
20877
+ label: "Default View"
20878
+ },
20879
+ {
20880
+ type: "toggle",
20881
+ key: "calendarShowWeekNumbers",
20882
+ label: "Show Week Numbers"
20883
+ }
20884
+ ]
20885
+ },
20886
+ map: {
20887
+ key: "map",
20888
+ label: "Map",
20889
+ icon: "map",
20890
+ description: "Collaborative world map with shared markers",
20891
+ core: true,
20892
+ supportsChildren: true,
20893
+ childLabel: "Location",
20894
+ defaultMetaFields: [{
20895
+ type: "toggle",
20896
+ key: "mapShowLabels",
20897
+ label: "Show Labels",
20898
+ default: true
20899
+ }]
20900
+ },
20901
+ dashboard: {
20902
+ key: "dashboard",
20903
+ label: "Dashboard",
20904
+ icon: "layout-dashboard",
20905
+ description: "Arrange documents as draggable icons with optional widget views",
20906
+ core: true,
20907
+ supportsChildren: true,
20908
+ childLabel: "Item"
20909
+ },
20910
+ call: {
20911
+ key: "call",
20912
+ label: "Call",
20913
+ icon: "phone",
20914
+ description: "Video call room with live audio and screen sharing",
20915
+ core: true,
20916
+ supportsChildren: false
20917
+ },
20918
+ chart: {
20919
+ key: "chart",
20920
+ label: "Chart",
20921
+ icon: "bar-chart-3",
20922
+ description: "Charts — manual data points or aggregation over document trees",
20923
+ core: true,
20924
+ supportsChildren: true,
20925
+ childLabel: "Data Point",
20926
+ grandchildLabel: "Data Point",
20927
+ metaSchema: [
20928
+ {
20929
+ type: "number",
20930
+ key: "number",
20931
+ step: .01,
20932
+ label: "Value"
20933
+ },
20934
+ {
20935
+ type: "colorPreset",
20936
+ key: "color",
20937
+ presets: [
20938
+ "#6366f1",
20939
+ "#ec4899",
20940
+ "#f97316",
20941
+ "#22c55e",
20942
+ "#3b82f6",
20943
+ "#a855f7",
20944
+ "#14b8a6",
20945
+ "#eab308"
20946
+ ],
20947
+ label: "Color"
20948
+ },
20949
+ {
20950
+ type: "tags",
20951
+ key: "tags",
20952
+ label: "Tags"
20953
+ }
20954
+ ],
20955
+ defaultMetaFields: [
20956
+ {
20957
+ type: "select",
20958
+ key: "chartType",
20959
+ options: [
20960
+ "bar",
20961
+ "stacked bar",
20962
+ "line",
20963
+ "donut",
20964
+ "treemap"
20965
+ ],
20966
+ label: "Chart Type"
20967
+ },
20968
+ {
20969
+ type: "select",
20970
+ key: "chartMetric",
20971
+ options: [
20972
+ "value",
20973
+ "type",
20974
+ "tag",
20975
+ "status",
20976
+ "priority",
20977
+ "activity",
20978
+ "completion"
20979
+ ],
20980
+ label: "Metric"
20981
+ },
20982
+ {
20983
+ type: "select",
20984
+ key: "chartColorScheme",
20985
+ options: [
20986
+ "default",
20987
+ "warm",
20988
+ "cool",
20989
+ "mono"
20990
+ ],
20991
+ label: "Colors"
20992
+ },
20993
+ {
20994
+ type: "number",
20995
+ key: "chartLimit",
20996
+ min: 3,
20997
+ max: 30,
20998
+ step: 1,
20999
+ label: "Max Items"
21000
+ },
21001
+ {
21002
+ type: "toggle",
21003
+ key: "chartShowLegend",
21004
+ label: "Show Legend",
21005
+ default: true
21006
+ },
21007
+ {
21008
+ type: "toggle",
21009
+ key: "chartShowValues",
21010
+ label: "Show Values"
21011
+ }
21012
+ ]
21013
+ },
21014
+ sheets: {
21015
+ key: "sheets",
21016
+ label: "Sheets",
21017
+ icon: "grid-3x3",
21018
+ description: "Spreadsheet — cells, formulas, and formatting in a collaborative grid",
21019
+ core: true,
21020
+ supportsChildren: true,
21021
+ childLabel: "Column",
21022
+ grandchildLabel: "Cell",
21023
+ defaultMetaFields: [
21024
+ {
21025
+ type: "number",
21026
+ key: "sheetsDefaultColWidth",
21027
+ min: 40,
21028
+ max: 500,
21029
+ step: 10,
21030
+ label: "Column Width"
21031
+ },
21032
+ {
21033
+ type: "number",
21034
+ key: "sheetsDefaultRowHeight",
21035
+ min: 20,
21036
+ max: 100,
21037
+ step: 2,
21038
+ label: "Row Height"
21039
+ },
21040
+ {
21041
+ type: "toggle",
21042
+ key: "sheetsShowGridlines",
21043
+ label: "Gridlines"
21044
+ }
21045
+ ]
21046
+ },
21047
+ slides: {
21048
+ key: "slides",
21049
+ label: "Slides",
21050
+ icon: "presentation",
21051
+ description: "Presentation slides with two-axis navigation",
21052
+ core: true,
21053
+ supportsChildren: true,
21054
+ childLabel: "Slide",
21055
+ grandchildLabel: "Sub-slide",
21056
+ metaSchema: [{
21057
+ type: "select",
21058
+ key: "slidesTransition",
21059
+ options: [
21060
+ "none",
21061
+ "fade",
21062
+ "slide"
21063
+ ],
21064
+ label: "Transition"
21065
+ }, {
21066
+ type: "colorPreset",
21067
+ key: "color",
21068
+ presets: [
21069
+ "#6366f1",
21070
+ "#ec4899",
21071
+ "#f97316",
21072
+ "#22c55e",
21073
+ "#3b82f6",
21074
+ "#a855f7"
21075
+ ],
21076
+ label: "Accent"
21077
+ }],
21078
+ defaultMetaFields: [{
21079
+ type: "select",
21080
+ key: "slidesTheme",
21081
+ options: ["dark", "light"],
21082
+ label: "Theme"
21083
+ }]
21084
+ },
21085
+ overview: {
21086
+ key: "overview",
21087
+ label: "Overview",
21088
+ icon: "radar",
21089
+ description: "Space home — activity, people, stats, and health at a glance",
21090
+ core: true,
21091
+ supportsChildren: true,
21092
+ childLabel: "Page"
21093
+ },
21094
+ spatial: {
21095
+ key: "spatial",
21096
+ label: "Spatial",
21097
+ icon: "box",
21098
+ description: "3D scene with collaborative objects and real-time presence",
21099
+ core: false,
21100
+ plugin: "spatial",
21101
+ supportsChildren: true,
21102
+ childLabel: "Object",
21103
+ grandchildLabel: "Part",
21104
+ defaultDepth: -1,
21105
+ metaSchema: [
21106
+ {
21107
+ type: "select",
21108
+ key: "spShape",
21109
+ options: [
21110
+ "box",
21111
+ "sphere",
21112
+ "cylinder",
21113
+ "cone",
21114
+ "plane",
21115
+ "torus",
21116
+ "glb"
21117
+ ],
21118
+ label: "Shape"
21119
+ },
21120
+ {
21121
+ type: "colorPreset",
21122
+ key: "color",
21123
+ presets: [
21124
+ "#6366f1",
21125
+ "#ef4444",
21126
+ "#22c55e",
21127
+ "#3b82f6",
21128
+ "#f97316",
21129
+ "#a855f7",
21130
+ "#ec4899",
21131
+ "#14b8a6"
21132
+ ],
21133
+ label: "Color"
21134
+ },
21135
+ {
21136
+ type: "slider",
21137
+ key: "spOpacity",
21138
+ min: 0,
21139
+ max: 100,
21140
+ label: "Opacity"
21141
+ },
21142
+ {
21143
+ type: "number",
21144
+ key: "spX",
21145
+ step: .1,
21146
+ label: "X"
21147
+ },
21148
+ {
21149
+ type: "number",
21150
+ key: "spY",
21151
+ step: .1,
21152
+ label: "Y"
21153
+ },
21154
+ {
21155
+ type: "number",
21156
+ key: "spZ",
21157
+ step: .1,
21158
+ label: "Z"
21159
+ },
21160
+ {
21161
+ type: "number",
21162
+ key: "spRX",
21163
+ min: -180,
21164
+ max: 180,
21165
+ step: 1,
21166
+ label: "Rot X"
21167
+ },
21168
+ {
21169
+ type: "number",
21170
+ key: "spRY",
21171
+ min: -180,
21172
+ max: 180,
21173
+ step: 1,
21174
+ label: "Rot Y"
21175
+ },
21176
+ {
21177
+ type: "number",
21178
+ key: "spRZ",
21179
+ min: -180,
21180
+ max: 180,
21181
+ step: 1,
21182
+ label: "Rot Z"
21183
+ },
21184
+ {
21185
+ type: "number",
21186
+ key: "spSX",
21187
+ min: .01,
21188
+ max: 100,
21189
+ step: .1,
21190
+ label: "Scale X"
21191
+ },
21192
+ {
21193
+ type: "number",
21194
+ key: "spSY",
21195
+ min: .01,
21196
+ max: 100,
21197
+ step: .1,
21198
+ label: "Scale Y"
21199
+ },
21200
+ {
21201
+ type: "number",
21202
+ key: "spSZ",
21203
+ min: .01,
21204
+ max: 100,
21205
+ step: .1,
21206
+ label: "Scale Z"
21207
+ }
21208
+ ],
21209
+ defaultMetaFields: [{
21210
+ type: "toggle",
21211
+ key: "spatialGridVisible",
21212
+ label: "Show Grid",
21213
+ default: true
21214
+ }]
21215
+ },
21216
+ media: {
21217
+ key: "media",
21218
+ label: "Media",
21219
+ icon: "disc-3",
21220
+ description: "Media player with synced listening and playlists",
21221
+ core: false,
21222
+ plugin: "media",
21223
+ supportsChildren: true,
21224
+ childLabel: "Track",
21225
+ defaultDepth: -1,
21226
+ metaSchema: [{
21227
+ type: "tags",
21228
+ key: "tags",
21229
+ label: "Tags"
21230
+ }],
21231
+ defaultMetaFields: [{
21232
+ type: "select",
21233
+ key: "mediaRepeat",
21234
+ options: [
21235
+ "off",
21236
+ "all",
21237
+ "one"
21238
+ ],
21239
+ label: "Repeat"
21240
+ }, {
21241
+ type: "toggle",
21242
+ key: "mediaShuffle",
21243
+ label: "Shuffle"
21244
+ }]
21245
+ },
21246
+ coder: {
21247
+ key: "coder",
21248
+ label: "Coder",
21249
+ icon: "code-2",
21250
+ description: "Collaborative multi-file coding environment",
21251
+ core: false,
21252
+ plugin: "coder",
21253
+ supportsChildren: true,
21254
+ childLabel: "File",
21255
+ defaultDepth: -1,
21256
+ metaSchema: [{
21257
+ type: "select",
21258
+ key: "fileType",
21259
+ options: [
21260
+ "vue",
21261
+ "ts",
21262
+ "js",
21263
+ "css",
21264
+ "json",
21265
+ "folder"
21266
+ ],
21267
+ label: "Type"
21268
+ }, {
21269
+ type: "toggle",
21270
+ key: "entry",
21271
+ label: "Entry Point"
21272
+ }]
21273
+ }
21274
+ };
21275
+ const TYPE_ALIASES = { desktop: "dashboard" };
21276
+ function resolvePageType(key) {
21277
+ if (!key) return void 0;
21278
+ return PAGE_TYPES[TYPE_ALIASES[key] ?? key];
21279
+ }
21280
+
20434
21281
  //#endregion
20435
21282
  //#region packages/mcp/src/tools/tree.ts
20436
21283
  /**
@@ -20504,16 +21351,12 @@ function registerTreeTools(mcp, server) {
20504
21351
  server.setAutoStatus("reading");
20505
21352
  server.setActiveToolCall({ name: "list_documents" });
20506
21353
  const treeMap = server.getTreeMap();
20507
- if (!treeMap) {
20508
- server.setActiveToolCall(null);
20509
- return { content: [{
20510
- type: "text",
20511
- text: "Not connected"
20512
- }] };
20513
- }
21354
+ if (!treeMap) return { content: [{
21355
+ type: "text",
21356
+ text: "Not connected"
21357
+ }] };
20514
21358
  const targetId = normalizeRootId(parentId, server);
20515
21359
  const children = childrenOf$1(readEntries$1(treeMap), targetId);
20516
- server.setActiveToolCall(null);
20517
21360
  return { content: [{
20518
21361
  type: "text",
20519
21362
  text: JSON.stringify(children, null, 2)
@@ -20526,17 +21369,13 @@ function registerTreeTools(mcp, server) {
20526
21369
  server.setAutoStatus("reading");
20527
21370
  server.setActiveToolCall({ name: "get_document_tree" });
20528
21371
  const treeMap = server.getTreeMap();
20529
- if (!treeMap) {
20530
- server.setActiveToolCall(null);
20531
- return { content: [{
20532
- type: "text",
20533
- text: "Not connected"
20534
- }] };
20535
- }
21372
+ if (!treeMap) return { content: [{
21373
+ type: "text",
21374
+ text: "Not connected"
21375
+ }] };
20536
21376
  const targetId = normalizeRootId(rootId, server);
20537
21377
  const maxDepth = depth ?? 3;
20538
21378
  const tree = buildTree$1(readEntries$1(treeMap), targetId, maxDepth);
20539
- server.setActiveToolCall(null);
20540
21379
  return { content: [{
20541
21380
  type: "text",
20542
21381
  text: JSON.stringify(tree, null, 2)
@@ -20552,13 +21391,10 @@ function registerTreeTools(mcp, server) {
20552
21391
  target: query
20553
21392
  });
20554
21393
  const treeMap = server.getTreeMap();
20555
- if (!treeMap) {
20556
- server.setActiveToolCall(null);
20557
- return { content: [{
20558
- type: "text",
20559
- text: "Not connected"
20560
- }] };
20561
- }
21394
+ if (!treeMap) return { content: [{
21395
+ type: "text",
21396
+ text: "Not connected"
21397
+ }] };
20562
21398
  const entries = readEntries$1(treeMap);
20563
21399
  const lowerQuery = query.toLowerCase();
20564
21400
  const normalizedRoot = normalizeRootId(rootId, server);
@@ -20583,7 +21419,6 @@ function registerTreeTools(mcp, server) {
20583
21419
  path
20584
21420
  };
20585
21421
  });
20586
- server.setActiveToolCall(null);
20587
21422
  if (results.length === 0) return { content: [{
20588
21423
  type: "text",
20589
21424
  text: `No documents found matching "${query}". Try get_document_tree to see the full hierarchy.`
@@ -20596,8 +21431,8 @@ function registerTreeTools(mcp, server) {
20596
21431
  mcp.tool("create_document", "Create a new document in the tree. Returns the new document ID.", {
20597
21432
  parentId: z.string().optional().describe("Parent document ID. Omit for top-level pages. Use a document ID for nested/child pages."),
20598
21433
  label: z.string().describe("Display name / title for the document."),
20599
- type: 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."),
20600
- 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.")
21434
+ type: 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."),
21435
+ meta: z.record(z.string(), 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.")
20601
21436
  }, async ({ parentId, label, type, meta }) => {
20602
21437
  server.setAutoStatus("creating");
20603
21438
  server.setActiveToolCall({
@@ -20606,13 +21441,10 @@ function registerTreeTools(mcp, server) {
20606
21441
  });
20607
21442
  const treeMap = server.getTreeMap();
20608
21443
  const rootDoc = server.rootDocument;
20609
- if (!treeMap || !rootDoc) {
20610
- server.setActiveToolCall(null);
20611
- return { content: [{
20612
- type: "text",
20613
- text: "Not connected"
20614
- }] };
20615
- }
21444
+ if (!treeMap || !rootDoc) return { content: [{
21445
+ type: "text",
21446
+ text: "Not connected"
21447
+ }] };
20616
21448
  const id = crypto.randomUUID();
20617
21449
  const normalizedParent = normalizeRootId(parentId, server);
20618
21450
  const now = Date.now();
@@ -20628,7 +21460,6 @@ function registerTreeTools(mcp, server) {
20628
21460
  });
20629
21461
  });
20630
21462
  server.setFocusedDoc(id);
20631
- server.setActiveToolCall(null);
20632
21463
  return { content: [{
20633
21464
  type: "text",
20634
21465
  text: JSON.stringify({
@@ -20649,28 +21480,21 @@ function registerTreeTools(mcp, server) {
20649
21480
  target: id
20650
21481
  });
20651
21482
  const treeMap = server.getTreeMap();
20652
- if (!treeMap) {
20653
- server.setActiveToolCall(null);
20654
- return { content: [{
20655
- type: "text",
20656
- text: "Not connected"
20657
- }] };
20658
- }
21483
+ if (!treeMap) return { content: [{
21484
+ type: "text",
21485
+ text: "Not connected"
21486
+ }] };
20659
21487
  const raw = treeMap.get(id);
20660
- if (!raw) {
20661
- server.setActiveToolCall(null);
20662
- return { content: [{
20663
- type: "text",
20664
- text: `Document ${id} not found`
20665
- }] };
20666
- }
21488
+ if (!raw) return { content: [{
21489
+ type: "text",
21490
+ text: `Document ${id} not found`
21491
+ }] };
20667
21492
  const entry = toPlain(raw);
20668
21493
  treeMap.set(id, {
20669
21494
  ...entry,
20670
21495
  label,
20671
21496
  updatedAt: Date.now()
20672
21497
  });
20673
- server.setActiveToolCall(null);
20674
21498
  return { content: [{
20675
21499
  type: "text",
20676
21500
  text: `Renamed to "${label}"`
@@ -20687,21 +21511,15 @@ function registerTreeTools(mcp, server) {
20687
21511
  target: id
20688
21512
  });
20689
21513
  const treeMap = server.getTreeMap();
20690
- if (!treeMap) {
20691
- server.setActiveToolCall(null);
20692
- return { content: [{
20693
- type: "text",
20694
- text: "Not connected"
20695
- }] };
20696
- }
21514
+ if (!treeMap) return { content: [{
21515
+ type: "text",
21516
+ text: "Not connected"
21517
+ }] };
20697
21518
  const raw = treeMap.get(id);
20698
- if (!raw) {
20699
- server.setActiveToolCall(null);
20700
- return { content: [{
20701
- type: "text",
20702
- text: `Document ${id} not found`
20703
- }] };
20704
- }
21519
+ if (!raw) return { content: [{
21520
+ type: "text",
21521
+ text: `Document ${id} not found`
21522
+ }] };
20705
21523
  const entry = toPlain(raw);
20706
21524
  treeMap.set(id, {
20707
21525
  ...entry,
@@ -20709,7 +21527,6 @@ function registerTreeTools(mcp, server) {
20709
21527
  order: order ?? Date.now(),
20710
21528
  updatedAt: Date.now()
20711
21529
  });
20712
- server.setActiveToolCall(null);
20713
21530
  return { content: [{
20714
21531
  type: "text",
20715
21532
  text: `Moved ${id} to parent ${newParentId}`
@@ -20724,13 +21541,10 @@ function registerTreeTools(mcp, server) {
20724
21541
  const treeMap = server.getTreeMap();
20725
21542
  const trashMap = server.getTrashMap();
20726
21543
  const rootDoc = server.rootDocument;
20727
- if (!treeMap || !trashMap || !rootDoc) {
20728
- server.setActiveToolCall(null);
20729
- return { content: [{
20730
- type: "text",
20731
- text: "Not connected"
20732
- }] };
20733
- }
21544
+ if (!treeMap || !trashMap || !rootDoc) return { content: [{
21545
+ type: "text",
21546
+ text: "Not connected"
21547
+ }] };
20734
21548
  const toDelete = [id, ...descendantsOf(readEntries$1(treeMap), id).map((e) => e.id)];
20735
21549
  const now = Date.now();
20736
21550
  rootDoc.transact(() => {
@@ -20749,7 +21563,6 @@ function registerTreeTools(mcp, server) {
20749
21563
  treeMap.delete(nid);
20750
21564
  }
20751
21565
  });
20752
- server.setActiveToolCall(null);
20753
21566
  return { content: [{
20754
21567
  type: "text",
20755
21568
  text: `Deleted ${toDelete.length} document(s)`
@@ -20757,7 +21570,7 @@ function registerTreeTools(mcp, server) {
20757
21570
  });
20758
21571
  mcp.tool("change_document_type", "Change the page type view of a document (data is preserved).", {
20759
21572
  id: z.string().describe("Document ID."),
20760
- type: z.string().describe("New page type: \"doc\", \"kanban\", \"table\", \"calendar\", \"timeline\", \"checklist\", \"outline\", \"gallery\", \"map\", \"graph\", \"dashboard\", \"spatial\", \"media\", \"slides\", \"chart\", \"sheets\", \"overview\", \"call\".")
21573
+ type: 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.")
20761
21574
  }, async ({ id, type }) => {
20762
21575
  server.setAutoStatus("writing");
20763
21576
  server.setActiveToolCall({
@@ -20765,28 +21578,21 @@ function registerTreeTools(mcp, server) {
20765
21578
  target: id
20766
21579
  });
20767
21580
  const treeMap = server.getTreeMap();
20768
- if (!treeMap) {
20769
- server.setActiveToolCall(null);
20770
- return { content: [{
20771
- type: "text",
20772
- text: "Not connected"
20773
- }] };
20774
- }
21581
+ if (!treeMap) return { content: [{
21582
+ type: "text",
21583
+ text: "Not connected"
21584
+ }] };
20775
21585
  const raw = treeMap.get(id);
20776
- if (!raw) {
20777
- server.setActiveToolCall(null);
20778
- return { content: [{
20779
- type: "text",
20780
- text: `Document ${id} not found`
20781
- }] };
20782
- }
21586
+ if (!raw) return { content: [{
21587
+ type: "text",
21588
+ text: `Document ${id} not found`
21589
+ }] };
20783
21590
  const entry = toPlain(raw);
20784
21591
  treeMap.set(id, {
20785
21592
  ...entry,
20786
21593
  type,
20787
21594
  updatedAt: Date.now()
20788
21595
  });
20789
- server.setActiveToolCall(null);
20790
21596
  return { content: [{
20791
21597
  type: "text",
20792
21598
  text: `Changed type to "${type}"`
@@ -20842,6 +21648,27 @@ function registerTreeTools(mcp, server) {
20842
21648
  }, null, 2)
20843
21649
  }] };
20844
21650
  });
21651
+ 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: 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 }) => {
21652
+ if (key) {
21653
+ const resolved = resolvePageType(key);
21654
+ if (!resolved) return { content: [{
21655
+ type: "text",
21656
+ text: `Unknown page type "${key}". Call list_page_types with no args to see all types.`
21657
+ }] };
21658
+ return { content: [{
21659
+ type: "text",
21660
+ text: JSON.stringify(resolved, null, 2)
21661
+ }] };
21662
+ }
21663
+ const all = Object.values(PAGE_TYPES);
21664
+ return { content: [{
21665
+ type: "text",
21666
+ text: JSON.stringify({
21667
+ types: all,
21668
+ aliases: TYPE_ALIASES
21669
+ }, null, 2)
21670
+ }] };
21671
+ });
20845
21672
  }
20846
21673
 
20847
21674
  //#endregion
@@ -22018,7 +22845,6 @@ function registerContentTools(mcp, server) {
22018
22845
  });
22019
22846
  children.sort((a, b) => (treeMap.get(a.id)?.order ?? 0) - (treeMap.get(b.id)?.order ?? 0));
22020
22847
  }
22021
- server.setActiveToolCall(null);
22022
22848
  const result = {
22023
22849
  label,
22024
22850
  type,
@@ -22031,7 +22857,6 @@ function registerContentTools(mcp, server) {
22031
22857
  text: JSON.stringify(result, null, 2)
22032
22858
  }] };
22033
22859
  } catch (error) {
22034
- server.setActiveToolCall(null);
22035
22860
  return {
22036
22861
  content: [{
22037
22862
  type: "text",
@@ -22081,13 +22906,11 @@ function registerContentTools(mcp, server) {
22081
22906
  populateYDocFromMarkdown(fragment, body || markdown, title || "Untitled");
22082
22907
  server.setFocusedDoc(docId);
22083
22908
  server.setDocCursor(docId, fragment.length);
22084
- server.setActiveToolCall(null);
22085
22909
  return { content: [{
22086
22910
  type: "text",
22087
22911
  text: `Document ${docId} updated (${writeMode} mode)`
22088
22912
  }] };
22089
22913
  } catch (error) {
22090
- server.setActiveToolCall(null);
22091
22914
  return {
22092
22915
  content: [{
22093
22916
  type: "text",
@@ -22109,22 +22932,15 @@ function registerMetaTools(mcp, server) {
22109
22932
  target: docId
22110
22933
  });
22111
22934
  const treeMap = server.getTreeMap();
22112
- if (!treeMap) {
22113
- server.setActiveToolCall(null);
22114
- return { content: [{
22115
- type: "text",
22116
- text: "Not connected"
22117
- }] };
22118
- }
22935
+ if (!treeMap) return { content: [{
22936
+ type: "text",
22937
+ text: "Not connected"
22938
+ }] };
22119
22939
  const entry = treeMap.get(docId);
22120
- if (!entry) {
22121
- server.setActiveToolCall(null);
22122
- return { content: [{
22123
- type: "text",
22124
- text: `Document ${docId} not found`
22125
- }] };
22126
- }
22127
- server.setActiveToolCall(null);
22940
+ if (!entry) return { content: [{
22941
+ type: "text",
22942
+ text: `Document ${docId} not found`
22943
+ }] };
22128
22944
  return { content: [{
22129
22945
  type: "text",
22130
22946
  text: JSON.stringify({
@@ -22137,7 +22953,7 @@ function registerMetaTools(mcp, server) {
22137
22953
  });
22138
22954
  mcp.tool("update_metadata", "Update metadata fields on a document. Merges the provided fields into existing metadata.", {
22139
22955
  docId: z.string().describe("Document ID."),
22140
- meta: z.record(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.")
22956
+ meta: z.record(z.string(), 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.")
22141
22957
  }, async ({ docId, meta }) => {
22142
22958
  server.setAutoStatus("writing", docId);
22143
22959
  server.setActiveToolCall({
@@ -22145,21 +22961,15 @@ function registerMetaTools(mcp, server) {
22145
22961
  target: docId
22146
22962
  });
22147
22963
  const treeMap = server.getTreeMap();
22148
- if (!treeMap) {
22149
- server.setActiveToolCall(null);
22150
- return { content: [{
22151
- type: "text",
22152
- text: "Not connected"
22153
- }] };
22154
- }
22964
+ if (!treeMap) return { content: [{
22965
+ type: "text",
22966
+ text: "Not connected"
22967
+ }] };
22155
22968
  const entry = treeMap.get(docId);
22156
- if (!entry) {
22157
- server.setActiveToolCall(null);
22158
- return { content: [{
22159
- type: "text",
22160
- text: `Document ${docId} not found`
22161
- }] };
22162
- }
22969
+ if (!entry) return { content: [{
22970
+ type: "text",
22971
+ text: `Document ${docId} not found`
22972
+ }] };
22163
22973
  treeMap.set(docId, {
22164
22974
  ...entry,
22165
22975
  meta: {
@@ -22168,7 +22978,6 @@ function registerMetaTools(mcp, server) {
22168
22978
  },
22169
22979
  updatedAt: Date.now()
22170
22980
  });
22171
- server.setActiveToolCall(null);
22172
22981
  return { content: [{
22173
22982
  type: "text",
22174
22983
  text: `Metadata updated for ${docId}`
@@ -22183,6 +22992,11 @@ function registerMetaTools(mcp, server) {
22183
22992
  */
22184
22993
  function registerFileTools(mcp, server) {
22185
22994
  mcp.tool("list_uploads", "List file attachments for a document.", { docId: z.string().describe("Document ID.") }, async ({ docId }) => {
22995
+ server.setAutoStatus("reading", docId);
22996
+ server.setActiveToolCall({
22997
+ name: "list_uploads",
22998
+ target: docId
22999
+ });
22186
23000
  try {
22187
23001
  const uploads = await server.client.listUploads(docId);
22188
23002
  return { content: [{
@@ -22204,6 +23018,11 @@ function registerFileTools(mcp, server) {
22204
23018
  filePath: z.string().describe("Absolute path to the local file to upload."),
22205
23019
  filename: z.string().optional().describe("Override filename (defaults to basename of filePath).")
22206
23020
  }, async ({ docId, filePath, filename }) => {
23021
+ server.setAutoStatus("uploading", docId);
23022
+ server.setActiveToolCall({
23023
+ name: "upload_file",
23024
+ target: path.basename(filePath)
23025
+ });
22207
23026
  try {
22208
23027
  const resolvedPath = path.resolve(filePath);
22209
23028
  const data = fs.readFileSync(resolvedPath);
@@ -22229,6 +23048,11 @@ function registerFileTools(mcp, server) {
22229
23048
  uploadId: z.string().describe("Upload ID to download."),
22230
23049
  saveTo: z.string().describe("Absolute local file path to save the download.")
22231
23050
  }, async ({ docId, uploadId, saveTo }) => {
23051
+ server.setAutoStatus("reading", docId);
23052
+ server.setActiveToolCall({
23053
+ name: "download_file",
23054
+ target: path.basename(saveTo)
23055
+ });
22232
23056
  try {
22233
23057
  const blob = await server.client.getUpload(docId, uploadId);
22234
23058
  const buffer = Buffer.from(await blob.arrayBuffer());
@@ -22252,6 +23076,11 @@ function registerFileTools(mcp, server) {
22252
23076
  docId: z.string().describe("Document ID."),
22253
23077
  uploadId: z.string().describe("Upload ID to delete.")
22254
23078
  }, async ({ docId, uploadId }) => {
23079
+ server.setAutoStatus("writing", docId);
23080
+ server.setActiveToolCall({
23081
+ name: "delete_file",
23082
+ target: uploadId
23083
+ });
22255
23084
  try {
22256
23085
  await server.client.deleteUpload(docId, uploadId);
22257
23086
  return { content: [{
@@ -22293,6 +23122,10 @@ function registerAwarenessTools(mcp, server) {
22293
23122
  docId: z.string().describe("Document ID to set awareness on."),
22294
23123
  fields: z.record(z.string(), 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.")
22295
23124
  }, async ({ docId, fields }) => {
23125
+ server.setActiveToolCall({
23126
+ name: "set_doc_awareness",
23127
+ target: docId
23128
+ });
22296
23129
  try {
22297
23130
  const provider = await server.getChildProvider(docId);
22298
23131
  for (const [key, value] of Object.entries(fields)) provider.awareness.setLocalStateField(key, value ?? null);
@@ -22311,6 +23144,7 @@ function registerAwarenessTools(mcp, server) {
22311
23144
  }
22312
23145
  });
22313
23146
  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 () => {
23147
+ server.setActiveToolCall({ name: "poll_inbox" });
22314
23148
  try {
22315
23149
  const treeMap = server.getTreeMap();
22316
23150
  const rootDocId = server.rootDocId;
@@ -22357,6 +23191,10 @@ function registerAwarenessTools(mcp, server) {
22357
23191
  }
22358
23192
  });
22359
23193
  mcp.tool("list_connected_users", "List all connected users and their awareness state. Shows who is online and what they are doing.", { docId: z.string().optional().describe("If provided, list users connected to this specific document. Otherwise lists users from root awareness.") }, async ({ docId }) => {
23194
+ server.setActiveToolCall({
23195
+ name: "list_connected_users",
23196
+ target: docId
23197
+ });
22360
23198
  try {
22361
23199
  let awareness;
22362
23200
  if (docId) awareness = (await server.getChildProvider(docId)).awareness;
@@ -22401,16 +23239,13 @@ function registerChannelTools(mcp, server) {
22401
23239
  });
22402
23240
  const treeMap = server.getTreeMap();
22403
23241
  const rootDoc = server.rootDocument;
22404
- if (!treeMap || !rootDoc) {
22405
- server.setActiveToolCall(null);
22406
- return {
22407
- content: [{
22408
- type: "text",
22409
- text: "Not connected"
22410
- }],
22411
- isError: true
22412
- };
22413
- }
23242
+ if (!treeMap || !rootDoc) return {
23243
+ content: [{
23244
+ type: "text",
23245
+ text: "Not connected"
23246
+ }],
23247
+ isError: true
23248
+ };
22414
23249
  const label = `AI Reply — ${(/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)}: ${text.slice(0, 40).replace(/\n/g, " ")}`;
22415
23250
  const replyId = crypto.randomUUID();
22416
23251
  const now = Date.now();
@@ -22426,7 +23261,6 @@ function registerChannelTools(mcp, server) {
22426
23261
  });
22427
23262
  populateYDocFromMarkdown((await server.getChildProvider(replyId)).document, text);
22428
23263
  if (task_id) server.clearAiTask(task_id);
22429
- server.setActiveToolCall(null);
22430
23264
  return { content: [{
22431
23265
  type: "text",
22432
23266
  text: JSON.stringify({
@@ -22435,7 +23269,6 @@ function registerChannelTools(mcp, server) {
22435
23269
  })
22436
23270
  }] };
22437
23271
  } catch (error) {
22438
- server.setActiveToolCall(null);
22439
23272
  return {
22440
23273
  content: [{
22441
23274
  type: "text",
@@ -22458,14 +23291,15 @@ function registerChannelTools(mcp, server) {
22458
23291
  }],
22459
23292
  isError: true
22460
23293
  };
23294
+ const normalized = text.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\n");
23295
+ server.setAutoStatus(null);
23296
+ server.sendTypingIndicator(channel);
22461
23297
  rootProvider.sendStateless(JSON.stringify({
22462
23298
  type: "chat:send",
22463
23299
  channel,
22464
- content: text,
23300
+ content: normalized,
22465
23301
  sender_name: server.agentName
22466
23302
  }));
22467
- server.setAutoStatus(null);
22468
- server.setActiveToolCall(null);
22469
23303
  return { content: [{
22470
23304
  type: "text",
22471
23305
  text: `Sent to ${channel}`
@@ -22620,16 +23454,13 @@ function registerSvgTools(mcp, server) {
22620
23454
  target: docId
22621
23455
  });
22622
23456
  const cleanSvg = sanitizeSvg(svg);
22623
- if (!cleanSvg) {
22624
- server.setActiveToolCall(null);
22625
- return {
22626
- content: [{
22627
- type: "text",
22628
- text: "Error: SVG markup was empty or entirely stripped by sanitizer."
22629
- }],
22630
- isError: true
22631
- };
22632
- }
23457
+ if (!cleanSvg) return {
23458
+ content: [{
23459
+ type: "text",
23460
+ text: "Error: SVG markup was empty or entirely stripped by sanitizer."
23461
+ }],
23462
+ isError: true
23463
+ };
22633
23464
  const doc = (await server.getChildProvider(docId)).document;
22634
23465
  const fragment = doc.getXmlFragment("default");
22635
23466
  doc.transact(() => {
@@ -22640,13 +23471,11 @@ function registerSvgTools(mcp, server) {
22640
23471
  fragment.insert(insertPos, [el]);
22641
23472
  });
22642
23473
  server.setFocusedDoc(docId);
22643
- server.setActiveToolCall(null);
22644
23474
  return { content: [{
22645
23475
  type: "text",
22646
23476
  text: `SVG inserted into document ${docId}${title ? ` ("${title}")` : ""}`
22647
23477
  }] };
22648
23478
  } catch (error) {
22649
- server.setActiveToolCall(null);
22650
23479
  return {
22651
23480
  content: [{
22652
23481
  type: "text",
@@ -22704,24 +23533,38 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
22704
23533
 
22705
23534
  ## Page Types Reference
22706
23535
 
23536
+ ### Core Types (always available)
23537
+
22707
23538
  | Type | Children Are | Grandchildren Are | Depth | Key Meta on Children |
22708
23539
  |------|-------------|-------------------|-------|---------------------|
22709
23540
  | **doc** | Sub-documents | Sub-sub-documents | ∞ | — |
22710
23541
  | **kanban** | Columns | Cards | 2 | color, icon on cards |
22711
23542
  | **table** | Columns | Cells (positional rows) | 2 | — |
22712
- | **calendar** | Events | — | 1 | datetimeStart, datetimeEnd, allDay, color |
23543
+ | **calendar** | Events | — | 1 | datetimeStart, datetimeEnd, allDay, color, icon |
22713
23544
  | **timeline** | Epics | Tasks | 2 | dateStart, dateEnd, taskProgress, color |
22714
23545
  | **checklist** | Tasks | Sub-tasks | ∞ | checked, priority, dateEnd |
22715
23546
  | **outline** | Items | Sub-items | ∞ | — |
22716
- | **mindmap** | Central nodes | Branches | ∞ | mmX, mmY |
22717
23547
  | **graph** | Nodes | — | 1 | graphX, graphY, graphPinned, color |
22718
- | **gallery** | Items | — | 1 | geoLat, geoLng, datetimeStart, tags |
23548
+ | **gallery** | Items | — | 1 | geoLat, geoLng, datetimeStart, tags, rating, icon, color |
22719
23549
  | **map** | Markers/Lines | Points (for lines) | 2 | geoType, geoLat, geoLng, icon, color |
22720
- | **dashboard** | Items | | 1 | deskX, deskY, deskMode |
22721
- | **spatial** | Objects | Sub-parts | | spShape, spX/Y/Z, spColor, spOpacity |
22722
- | **media** | Tracks | | 1 | tags |
22723
- | **slides** | Slides | | 1 | |
22724
- | **whiteboard** | Objects | — | 1 | wbX, wbY, wbW, wbH |
23550
+ | **slides** | Slides | Sub-slides | 2 | slidesTransition, color |
23551
+ | **dashboard** | Items | | 1 | deskX, deskY, deskZ, deskMode |
23552
+ | **chart** | Data points | Data points | 2 | number (value), color, tags |
23553
+ | **sheets** | Columns | Cells | 2 | formula, bold, italic, textColor, bgColor, align |
23554
+ | **overview** | Pages | — | 1 | |
23555
+ | **call** | — (no children) | — | 0 | — |
23556
+
23557
+ Alias: \`desktop\` → \`dashboard\`.
23558
+
23559
+ ### Plugin Types (require plugin enabled on the server)
23560
+
23561
+ | Type | Plugin | Children Are | Grandchildren Are | Depth | Key Meta on Children |
23562
+ |------|--------|-------------|-------------------|-------|---------------------|
23563
+ | **spatial** | spatial | Objects | Sub-parts | ∞ | spShape, spX/Y/Z, spRX/RY/RZ, spSX/SY/SZ, color, spOpacity |
23564
+ | **media** | media | Tracks | — | ∞ | tags |
23565
+ | **coder** | coder | Files/Folders | — | ∞ | fileType, entry |
23566
+
23567
+ > Spatial uses the universal \`color\` key for object color — there is no \`spColor\`.
22725
23568
 
22726
23569
  ---
22727
23570
 
@@ -22794,18 +23637,65 @@ If you are adding content to an existing space, call \`get_document_tree(rootId:
22794
23637
  4. Modes: \`"icon"\` (small), \`"widget-sm"\` (240×180), \`"widget-lg"\` (400×320)
22795
23638
  5. Grid uses 80px cells
22796
23639
 
22797
- **Spatial (3D scene):**
23640
+ **Spatial (3D scene)** *(requires spatial plugin):*
22798
23641
  1. \`create_document(parentId, "3D Scene", "spatial")\`
22799
23642
  2. Create objects: \`create_document(sceneId, "Red Cube")\`
22800
- 3. Set 3D properties: \`update_metadata(objId, { spShape: "box", spColor: "#ef4444", spX: 0, spY: 1, spZ: 0, spSX: 2, spSY: 2, spSZ: 2 })\`
22801
- 4. Shapes: \`"box"\`, \`"sphere"\`, \`"cylinder"\`, \`"cone"\`, \`"plane"\`, \`"torus"\`, \`"glb"\` (uploaded 3D model)
23643
+ 3. Set 3D properties: \`update_metadata(objId, { spShape: "box", color: "#ef4444", spX: 0, spY: 1, spZ: 0, spSX: 2, spSY: 2, spSZ: 2 })\`
23644
+ 4. Shapes: \`"box"\`, \`"sphere"\`, \`"cylinder"\`, \`"cone"\`, \`"plane"\`, \`"torus"\`, \`"glb"\` (uploaded 3D model — set \`spModelUploadId\` and \`spModelDocId\`)
22802
23645
  5. Rotation (degrees): \`spRX\`, \`spRY\`, \`spRZ\`. Scale: \`spSX\`, \`spSY\`, \`spSZ\` (default 1). Opacity: \`spOpacity\` (0–100)
23646
+ 6. Use the universal \`color\` key — **never \`spColor\`**
22803
23647
 
22804
23648
  **Outline (nested items):**
22805
23649
  1. \`create_document(parentId, "Meeting Notes", "outline")\`
22806
23650
  2. Create items: \`create_document(outlineId, "Agenda Item 1")\`
22807
23651
  3. Create sub-items (unlimited depth): \`create_document(itemId, "Sub-point")\`
22808
23652
 
23653
+ **Slides (presentation with two-axis nav):**
23654
+ 1. \`create_document(parentId, "Q1 Review", "slides")\`
23655
+ 2. Create slides as direct children: \`create_document(deckId, "Intro")\`
23656
+ 3. Create sub-slides (vertical navigation) as grandchildren: \`create_document(slideId, "Deep dive")\`
23657
+ 4. Per-slide transition: \`update_metadata(slideId, { slidesTransition: "fade", color: "#6366f1" })\`
23658
+ 5. Deck-level theme: \`update_metadata(deckId, { slidesTheme: "dark" })\`
23659
+
23660
+ **Chart (data viz — manual or aggregation):**
23661
+ 1. \`create_document(parentId, "Sales", "chart")\`
23662
+ 2. Configure: \`update_metadata(chartId, { chartType: "bar", chartMetric: "value", chartShowLegend: true, chartLimit: 10 })\`
23663
+ 3. Modes:
23664
+ - **Manual data points**: create children with \`number\` (value) and optional \`tags\`/\`color\`
23665
+ \`create_document(chartId, "Q1")\` then \`update_metadata(dpId, { number: 42500, color: "#6366f1" })\`
23666
+ - **Aggregation**: set \`chartMetric\` to \`"type"\`/\`"tag"\`/\`"status"\`/\`"priority"\`/\`"activity"\`/\`"completion"\` and point the chart at a subtree — it aggregates the descendants' meta automatically
23667
+ 4. Chart types: \`"bar"\`, \`"stacked bar"\`, \`"line"\`, \`"donut"\`, \`"treemap"\`
23668
+ 5. Color schemes: \`"default"\`, \`"warm"\`, \`"cool"\`, \`"mono"\`
23669
+
23670
+ **Sheets (spreadsheet with formulas):**
23671
+ 1. \`create_document(parentId, "Budget", "sheets")\`
23672
+ 2. Create columns: \`create_document(sheetId, "A")\`, \`create_document(sheetId, "B")\`
23673
+ 3. Create cells under columns (positional rows, like \`table\`)
23674
+ 4. Formulas on cells: \`update_metadata(cellId, { formula: "=A1+B1" })\`
23675
+ 5. Cell formatting: \`update_metadata(cellId, { bold: true, bgColor: "#fef3c7", align: "right" })\`
23676
+ 6. Deck config: \`update_metadata(sheetId, { sheetsDefaultColWidth: 120, sheetsDefaultRowHeight: 28, sheetsShowGridlines: true, sheetsFreezeRows: 1, sheetsFreezeCols: 1 })\`
23677
+
23678
+ **Overview (space home):**
23679
+ 1. \`create_document(parentId, "Home", "overview")\`
23680
+ 2. No children required — renders activity, people, stats from the surrounding space
23681
+ 3. Children (if any) show as linked pages
23682
+
23683
+ **Call (video room):**
23684
+ 1. \`create_document(parentId, "Daily Standup", "call")\`
23685
+ 2. Video rooms have **no children** — do not add documents underneath
23686
+
23687
+ **Coder (multi-file collaborative editor)** *(requires coder plugin):*
23688
+ 1. \`create_document(parentId, "My App", "coder")\`
23689
+ 2. Create files/folders: \`create_document(projectId, "App.vue")\`, \`create_document(projectId, "src")\`
23690
+ 3. Set file type: \`update_metadata(fileId, { fileType: "vue", entry: true })\`
23691
+ 4. \`fileType\` options: \`"vue"\`, \`"ts"\`, \`"js"\`, \`"css"\`, \`"json"\`, \`"folder"\`
23692
+ 5. Mark the entry file with \`entry: true\` — the renderer uses it as the preview root
23693
+
23694
+ **Media (audio/video playlist)** *(requires media plugin):*
23695
+ 1. \`create_document(parentId, "Focus Mix", "media")\`
23696
+ 2. Create tracks: \`create_document(playlistId, "Track 1")\` — attach audio/video file via upload tool
23697
+ 3. Playlist config: \`update_metadata(playlistId, { mediaRepeat: "all", mediaShuffle: false })\`
23698
+
22809
23699
  ---
22810
23700
 
22811
23701
  ## Document References
@@ -22880,22 +23770,55 @@ In the **graph** page type, document references (embeds and links) create visibl
22880
23770
  |-----|------|-----------|--------|
22881
23771
  | \`kanbanColumnWidth\` | string | kanban | "narrow", "default", "wide" |
22882
23772
  | \`galleryColumns\` | number | gallery | 1–6 |
22883
- | \`galleryAspect\` | string | gallery | "square", "4:3", "16:9" |
23773
+ | \`galleryAspect\` | string | gallery | "square", "4:3", "3:2", "16:9", "free" |
23774
+ | \`galleryCardStyle\` | string | gallery | "default", "compact", "detailed" |
23775
+ | \`galleryShowLabels\` | boolean | gallery | show item labels |
23776
+ | \`gallerySortBy\` | string | gallery | "manual", "date", "name", "rating" |
22884
23777
  | \`calendarView\` | string | calendar | "month", "week", "day" |
22885
23778
  | \`calendarWeekStart\` | string | calendar | "sun", "mon" |
23779
+ | \`calendarShowWeekNumbers\` | boolean | calendar | — |
22886
23780
  | \`tableMode\` | string | table | "hierarchy", "flat" |
23781
+ | \`tableSortKey\` | string | table | meta key to sort by |
23782
+ | \`tableSortDir\` | string | table | "asc", "desc" |
23783
+ | \`timelineZoom\` | string | timeline | "week", "month", "quarter" |
23784
+ | \`timelinePixelsPerDay\` | number | timeline | zoom granularity |
23785
+ | \`timelineCenterDate\` | string | timeline | ISO date to center view |
23786
+ | \`checklistFilter\` | string | checklist | "all", "active", "completed" |
23787
+ | \`checklistSort\` | string | checklist | "manual", "priority", "due" |
23788
+ | \`mapShowLabels\` | boolean | map | — |
23789
+ | \`graphSpacing\` | string | graph | "compact", "default", "spacious" |
23790
+ | \`graphShowLabels\` | boolean | graph | — |
23791
+ | \`graphEdgeThickness\` | string | graph | "thin", "normal", "thick" |
22887
23792
  | \`showRefEdges\` | boolean | graph | show doc-reference edges |
22888
-
22889
- ### Spatial 3D Keys (for spatial children)
23793
+ | \`mmSpacing\` | string | mindmap-layout | spacing between branches |
23794
+ | \`spatialGridVisible\` | boolean | spatial | show ground grid |
23795
+ | \`slidesTheme\` | string | slides | "dark", "light" |
23796
+ | \`chartType\` | string | chart | "bar", "stacked bar", "line", "donut", "treemap" |
23797
+ | \`chartMetric\` | string | chart | "value", "type", "tag", "status", "priority", "activity", "completion" |
23798
+ | \`chartColorScheme\` | string | chart | "default", "warm", "cool", "mono" |
23799
+ | \`chartLimit\` | number | chart | 3–30 (max items) |
23800
+ | \`chartShowLegend\` | boolean | chart | — |
23801
+ | \`chartShowValues\` | boolean | chart | — |
23802
+ | \`sheetsDefaultColWidth\` | number | sheets | 40–500 |
23803
+ | \`sheetsDefaultRowHeight\` | number | sheets | 20–100 |
23804
+ | \`sheetsShowGridlines\` | boolean | sheets | — |
23805
+ | \`sheetsFreezeRows\` | number | sheets | frozen rows count |
23806
+ | \`sheetsFreezeCols\` | number | sheets | frozen cols count |
23807
+ | \`mediaRepeat\` | string | media | "off", "all", "one" (plugin) |
23808
+ | \`mediaShuffle\` | boolean | media | plugin |
23809
+
23810
+ ### Spatial 3D Keys (for spatial children — requires spatial plugin)
22890
23811
 
22891
23812
  | Key | Type | Default | Meaning |
22892
23813
  |-----|------|---------|---------|
22893
23814
  | \`spShape\` | string | — | "box", "sphere", "cylinder", "cone", "plane", "torus", "glb" |
22894
- | \`spColor\` | string | — | CSS color |
23815
+ | \`color\` | string | — | CSS color — universal key, **not** \`spColor\` |
22895
23816
  | \`spOpacity\` | number | 100 | 0–100 |
22896
23817
  | \`spX\`, \`spY\`, \`spZ\` | number | 0 | Position |
22897
23818
  | \`spRX\`, \`spRY\`, \`spRZ\` | number | 0 | Rotation (degrees) |
22898
23819
  | \`spSX\`, \`spSY\`, \`spSZ\` | number | 1 | Scale |
23820
+ | \`spModelUploadId\` | string | — | GLB upload ID (when \`spShape: "glb"\`) |
23821
+ | \`spModelDocId\` | string | — | Doc ID that owns the GLB upload |
22899
23822
 
22900
23823
  ### Dashboard Keys (for dashboard children)
22901
23824
 
@@ -22905,6 +23828,28 @@ In the **graph** page type, document references (embeds and links) create visibl
22905
23828
  | \`deskZ\` | number | Z-index (layering) |
22906
23829
  | \`deskMode\` | string | "icon", "widget-sm" (240×180), "widget-lg" (400×320) |
22907
23830
 
23831
+ ### Sheets Cell Formatting (per-cell meta)
23832
+
23833
+ | Key | Type | Meaning |
23834
+ |-----|------|---------|
23835
+ | \`formula\` | string | Cell formula (e.g. "=A1+B1") |
23836
+ | \`bold\` | boolean | — |
23837
+ | \`italic\` | boolean | — |
23838
+ | \`textColor\` | string | CSS color |
23839
+ | \`bgColor\` | string | CSS color |
23840
+ | \`align\` | string | "left", "center", "right" |
23841
+
23842
+ ### Coder Keys (for coder children — plugin)
23843
+
23844
+ | Key | Type | Meaning |
23845
+ |-----|------|---------|
23846
+ | \`fileType\` | string | "vue", "ts", "js", "css", "json", "folder" |
23847
+ | \`entry\` | boolean | Mark this file as the preview entry point |
23848
+
23849
+ ### Discovering What Metadata Applies
23850
+
23851
+ 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.
23852
+
22908
23853
  ---
22909
23854
 
22910
23855
  ## Content Structure
@@ -22987,8 +23932,11 @@ Always clear fields when done by setting them to \`null\`.
22987
23932
  | **Outline** | \`outline:editing\` | nodeId | Editing an outline node |
22988
23933
  | **Gallery** | \`gallery:focused\` | itemId | Item hovered/selected |
22989
23934
  | **Timeline** | \`timeline:focused\` | taskId | Task selected |
22990
- | **Mindmap** | \`mindmap:focused\` | nodeId | Node selected/edited |
22991
23935
  | **Graph** | \`graph:focused\` | nodeId | Node hovered/selected |
23936
+ | **Slides** | \`slides:current\` | slideId | Slide being presented |
23937
+ | **Spatial** | \`spatial:selected\`, \`spatial:camera\` | objectId / camera state | plugin |
23938
+ | **Media** | \`media:playing\`, \`media:position\` | trackId / 0–1 | plugin |
23939
+ | **Coder** | \`coder:activeFile\` | fileId | plugin |
22992
23940
  | **Map** | \`map:focused\` | markerId | Marker hovered/selected |
22993
23941
  | **Doc** | \`doc:scroll\` | 0–1 number | Scroll position in document |
22994
23942
 
@@ -23331,6 +24279,9 @@ var HookBridge = class {
23331
24279
  }
23332
24280
  routeEvent(payload) {
23333
24281
  switch (payload.hook_event_name) {
24282
+ case "UserPromptSubmit":
24283
+ this.onUserPromptSubmit();
24284
+ break;
23334
24285
  case "PreToolUse":
23335
24286
  this.onPreToolUse(payload);
23336
24287
  break;
@@ -23341,13 +24292,18 @@ var HookBridge = class {
23341
24292
  this.onSubagentStart(payload);
23342
24293
  break;
23343
24294
  case "SubagentStop":
23344
- this.onSubagentStop(payload);
24295
+ this.onSubagentStop();
23345
24296
  break;
23346
24297
  case "Stop":
23347
24298
  this.onStop();
23348
24299
  break;
23349
24300
  }
23350
24301
  }
24302
+ /** New user turn — reset any lingering status/tool state from the previous turn. */
24303
+ onUserPromptSubmit() {
24304
+ this.server.setAutoStatus(null);
24305
+ this.server.setActiveToolCall(null);
24306
+ }
23351
24307
  onPreToolUse(payload) {
23352
24308
  const toolName = payload.tool_name ?? "";
23353
24309
  if (toolName.startsWith("mcp__abracadabra__")) return;
@@ -23359,7 +24315,6 @@ var HookBridge = class {
23359
24315
  }
23360
24316
  onPostToolUse(payload) {
23361
24317
  if ((payload.tool_name ?? "").startsWith("mcp__abracadabra__")) return;
23362
- this.server.setAutoStatus("thinking");
23363
24318
  }
23364
24319
  onSubagentStart(payload) {
23365
24320
  const agentType = payload.agent_type ?? "agent";
@@ -23369,9 +24324,7 @@ var HookBridge = class {
23369
24324
  });
23370
24325
  this.server.setAutoStatus("thinking");
23371
24326
  }
23372
- onSubagentStop(_payload) {
23373
- this.server.setAutoStatus("thinking");
23374
- }
24327
+ onSubagentStop() {}
23375
24328
  onStop() {
23376
24329
  this.server.setAutoStatus(null);
23377
24330
  this.server.setActiveToolCall(null);
@@ -23384,11 +24337,19 @@ var HookBridge = class {
23384
24337
  * Abracadabra MCP Server — entry point.
23385
24338
  *
23386
24339
  * Environment variables:
23387
- * ABRA_URL (required) — Server URL (e.g. http://localhost:1234)
24340
+ * ABRA_URL (required) — Server URL (e.g. http://localhost:1234)
23388
24341
  * ABRA_AGENT_NAME — Display name (default: "AI Assistant")
23389
24342
  * ABRA_AGENT_COLOR — HSL color for presence (default: "hsl(270, 80%, 60%)")
23390
24343
  * ABRA_INVITE_CODE — Invite code for first-run registration (grants role)
23391
24344
  * ABRA_KEY_FILE — Path to Ed25519 key file (default: ~/.abracadabra/agent.key)
24345
+ * ABRA_AGENT_TRIGGER_MODE — When to respond in group chats:
24346
+ * all → every message (legacy)
24347
+ * mention → only when @<alias> is used
24348
+ * task → only ai:task awareness events
24349
+ * mention+task → mention OR ai:task (default)
24350
+ * DMs always trigger regardless of mode.
24351
+ * ABRA_AGENT_MENTION_ALIASES — Comma-separated aliases for @mentions
24352
+ * (default: [ABRA_AGENT_NAME])
23392
24353
  */
23393
24354
  async function main() {
23394
24355
  const url = process.env.ABRA_URL;
@@ -23396,13 +24357,27 @@ async function main() {
23396
24357
  console.error("Missing required environment variable: ABRA_URL");
23397
24358
  process.exit(1);
23398
24359
  }
24360
+ const rawMode = (process.env.ABRA_AGENT_TRIGGER_MODE ?? "mention+task").trim().toLowerCase();
24361
+ const validModes = [
24362
+ "all",
24363
+ "mention",
24364
+ "task",
24365
+ "mention+task"
24366
+ ];
24367
+ const triggerMode = validModes.includes(rawMode) ? rawMode : "mention+task";
24368
+ if (rawMode && !validModes.includes(rawMode)) console.error(`[abracadabra-mcp] Invalid ABRA_AGENT_TRIGGER_MODE="${rawMode}", falling back to "mention+task"`);
24369
+ const aliasEnv = process.env.ABRA_AGENT_MENTION_ALIASES;
24370
+ const mentionAliases = aliasEnv ? aliasEnv.split(",").map((a) => a.trim()).filter((a) => a.length > 0) : void 0;
23399
24371
  const server = new AbracadabraMCPServer({
23400
24372
  url,
23401
24373
  agentName: process.env.ABRA_AGENT_NAME,
23402
24374
  agentColor: process.env.ABRA_AGENT_COLOR,
23403
24375
  inviteCode: process.env.ABRA_INVITE_CODE,
23404
- keyFile: process.env.ABRA_KEY_FILE
24376
+ keyFile: process.env.ABRA_KEY_FILE,
24377
+ triggerMode,
24378
+ mentionAliases
23405
24379
  });
24380
+ console.error(`[abracadabra-mcp] Trigger mode: ${triggerMode}; aliases: ${server.mentionAliases.join(", ")}`);
23406
24381
  const mcp = new McpServer({
23407
24382
  name: "abracadabra",
23408
24383
  version: "1.0.0"