{"version":3,"sources":["../src/mcp/server.ts","../src/undo/undo-tools.ts","../src/undo/use-undo-stack.ts"],"names":["undoOne","redoOne","readHistory","useState","useEffect","useCallback"],"mappings":"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","file":"undo.cjs","sourcesContent":["import {\n  type CallToolResult,\n  type JsonObject,\n  type JsonRpcMessage,\n  type JsonRpcRequest,\n  type JsonRpcId,\n  type RegisteredTool,\n  type ServerCapabilities,\n  type ServerInfo,\n  type ToolDefinition,\n  type ToolHandler,\n  JSONRPC_INTERNAL_ERROR,\n  JSONRPC_INVALID_PARAMS,\n  JSONRPC_METHOD_NOT_FOUND,\n  MCP_PROTOCOL_VERSION,\n} from \"./types\";\nimport { ToolRegistry } from \"./tool-host\";\n\nexport type McpServerOptions = {\n  info: ServerInfo;\n  /** Defaults to { tools: { listChanged: true } } */\n  capabilities?: ServerCapabilities;\n  /** Free-text instructions surfaced to clients during initialize. */\n  instructions?: string;\n  /**\n   * The protocol revisions this server speaks, NEWEST FIRST.\n   *\n   * `initialize` echoes the client's requested revision when it is in this\n   * list, and answers the first entry otherwise — the spec's rule, which leaves\n   * the client to decide whether it can live with the answer.\n   *\n   * Defaults to `[MCP_PROTOCOL_VERSION]`: one revision, answered whatever the\n   * client asks, which is exactly what this server did before the option\n   * existed. Only list a revision the tools you register are correct under.\n   */\n  protocolVersions?: readonly string[];\n};\n\nexport type Transport = {\n  /** Called by the server when it has a message to deliver to the client. */\n  send: (message: JsonRpcMessage) => void;\n  /** Called by the server when it's torn down so the transport can clean up. */\n  close?: () => void;\n};\n\n/**\n * MicroMcpServer — protocol-level MCP server, transport-agnostic.\n *\n * Use it like:\n *\n *   const server = new MicroMcpServer({ info: { name: \"session\", version: \"0.1\" } });\n *   server.registerTool({ name: \"...\", inputSchema: { type: \"object\" } }, async (args) => ({...}));\n *   const transport = new InProcessTransport();\n *   server.attach(transport);\n *   transport.deliver({ ... }); // client → server frames\n *\n * The same server can serve multiple transports (e.g. an in-process agent\n * AND a relayed external client) by attaching each one.\n */\nexport class MicroMcpServer extends ToolRegistry {\n  private transports = new Set<Transport>();\n  /**\n   * Transports owed a `tools/list_changed` at the end of this tick. Empty when\n   * none is scheduled. See {@link scheduleListChangedNotification}.\n   */\n  private listChangedOwed = new Set<Transport>();\n\n  readonly info: ServerInfo;\n  readonly capabilities: ServerCapabilities;\n  readonly instructions?: string;\n  readonly protocolVersions: readonly string[];\n\n  constructor(options: McpServerOptions) {\n    super();\n    this.info = options.info;\n    this.capabilities = options.capabilities ?? { tools: { listChanged: true } };\n    this.instructions = options.instructions;\n    if (options.protocolVersions && options.protocolVersions.length === 0) {\n      // No answer exists for a client's initialize. Fail here, next to the\n      // mistake, rather than in a handshake nobody is watching.\n      throw new Error(\"MicroMcpServer: protocolVersions is empty — a server must speak at least one protocol revision.\");\n    }\n    this.protocolVersions = options.protocolVersions ? [...options.protocolVersions] : [MCP_PROTOCOL_VERSION];\n  }\n\n  attach(transport: Transport): () => void {\n    this.transports.add(transport);\n    return () => this.detach(transport);\n  }\n\n  detach(transport: Transport): void {\n    if (this.transports.delete(transport)) {\n      transport.close?.();\n    }\n  }\n\n  unregisterTool(name: string): void {\n    if (this.tools.delete(name)) {\n      this.scheduleListChangedNotification();\n    }\n  }\n\n  protected onToolsChanged(): void {\n    this.scheduleListChangedNotification();\n  }\n\n  /**\n   * Receive a JSON-RPC frame from a client (called by the transport).\n   * The transport is responsible for sending the response back.\n   */\n  async receive(transport: Transport, message: JsonRpcMessage): Promise<void> {\n    if (!(\"method\" in message)) return; // It's a response, not a request — ignore.\n\n    const isNotification = !(\"id\" in message);\n    if (isNotification) {\n      // Notifications are fire-and-forget. We ignore unknown methods.\n      return;\n    }\n\n    const request = message as JsonRpcRequest;\n    try {\n      const result = await this.handle(request, transport);\n      transport.send({ jsonrpc: \"2.0\", id: request.id, result });\n    } catch (err) {\n      transport.send({\n        jsonrpc: \"2.0\",\n        id: request.id,\n        error: this.toRpcError(err),\n      });\n    }\n  }\n\n  private async handle(request: JsonRpcRequest, transport: Transport): Promise<any> {\n    const { method, params } = request;\n    switch (method) {\n      case \"initialize\":\n        return {\n          protocolVersion: this.negotiate(params?.protocolVersion),\n          capabilities: this.capabilities,\n          serverInfo: this.info,\n          ...(this.instructions ? { instructions: this.instructions } : {}),\n        };\n\n      case \"tools/list\":\n        return { tools: this.listTools() };\n\n      case \"tools/call\": {\n        const name = params?.name;\n        const args = (params?.arguments ?? {}) as JsonObject;\n        if (typeof name !== \"string\") {\n          throw rpcError(JSONRPC_INVALID_PARAMS, \"tools/call requires `name`\");\n        }\n        const tool = this.tools.get(name);\n        if (!tool) {\n          throw rpcError(JSONRPC_METHOD_NOT_FOUND, this.missingToolMessage(name));\n        }\n        const result = await tool.handler(args, { transport });\n        return result satisfies CallToolResult;\n      }\n\n      case \"ping\":\n        return {};\n\n      default:\n        throw rpcError(JSONRPC_METHOD_NOT_FOUND, `Unsupported method: ${method}`);\n    }\n  }\n\n  /** The revision to answer `initialize` with: the one asked for if spoken, else the newest. */\n  private negotiate(requested: unknown): string {\n    return typeof requested === \"string\" && this.protocolVersions.includes(requested)\n      ? requested\n      : this.protocolVersions[0];\n  }\n\n  /**\n   * Tell the transports that SAW the old tool list that it changed.\n   *\n   * Changes in one tick coalesce into one notification. The recipients are the\n   * transports attached at the moment of a change, not at the moment the\n   * notification goes out: a transport attached afterwards never had the old\n   * list, so \"your list is stale\" is noise to it — and on stdio, where a host\n   * builds the server and attaches stdin/stdout in the same tick, it was the\n   * first frame a client received, before it had even sent `initialize`.\n   */\n  private scheduleListChangedNotification(): void {\n    if (this.transports.size === 0) return;\n    const scheduled = this.listChangedOwed.size > 0;\n    for (const t of this.transports) this.listChangedOwed.add(t);\n    if (scheduled) return;\n    queueMicrotask(() => {\n      const owed = this.listChangedOwed;\n      this.listChangedOwed = new Set();\n      for (const t of owed) {\n        // Detached since the change: it is not listening any more.\n        if (this.transports.has(t)) t.send({ jsonrpc: \"2.0\", method: \"notifications/tools/list_changed\" });\n      }\n    });\n  }\n\n  /** Send an application notification to every client, or one attached transport. */\n  notify(message: JsonRpcMessage, transport?: Transport): void {\n    if (transport) transport.send(message);\n    else this.broadcast(message);\n  }\n\n  private broadcast(message: JsonRpcMessage): void {\n    for (const t of this.transports) t.send(message);\n  }\n\n  private toRpcError(err: unknown): { code: number; message: string; data?: any } {\n    if (err && typeof err === \"object\" && \"code\" in err && \"message\" in err) {\n      return err as any;\n    }\n    return {\n      code: JSONRPC_INTERNAL_ERROR,\n      message: err instanceof Error ? err.message : String(err),\n    };\n  }\n}\n\nexport function rpcError(code: number, message: string, data?: any) {\n  return { code, message, ...(data !== undefined ? { data } : {}) };\n}\n\n/**\n * Helper to build a CallToolResult from a string or structured value.\n */\nexport function textResult(text: string, structured?: any): CallToolResult {\n  return {\n    content: [{ type: \"text\", text }],\n    ...(structured !== undefined ? { structuredContent: structured } : {}),\n  };\n}\n\nexport function errorResult(text: string): CallToolResult {\n  return { content: [{ type: \"text\", text }], isError: true };\n}\n\n// Internal helper so the JsonRpcId import isn't dropped by tsup\ntype _KeepIdImport = JsonRpcId;\n","import { textResult, errorResult } from \"../mcp/server\";\nimport type { ToolHost } from \"../mcp/tool-host\";\nimport { readHistory, redoOne, undoOne } from \"./undo-stack\";\n\nexport type UndoToolsOptions = {\n  /** Default agent id when the caller doesn't pass one. */\n  defaultAgentId?: string;\n};\n\n/**\n * Idempotent tracker so multiple bridges on the same server only register\n * agent_undo / agent_redo / agent_history once.\n */\nconst installedHosts = new WeakSet<ToolHost>();\n\n/**\n * The one owner every copy of the undo tools registers under. They are SHARED\n * on purpose — every bridge ensures them, and a host may also call\n * `registerUndoTools` itself — so all of those are the same registrant, and a\n * second registration replaces the first instead of being refused as a\n * collision. Anything else claiming `agent_undo` is still refused.\n */\nconst UNDO_TOOLS_OWNER = \"agent-undo\";\n\n/**\n * ensureUndoToolsRegistered — bridges call this on construction. Safe to\n * call repeatedly with the same server; subsequent calls are no-ops.\n */\nexport function ensureUndoToolsRegistered(host: ToolHost, options: UndoToolsOptions = {}): void {\n  if (installedHosts.has(host)) return;\n  installedHosts.add(host);\n  registerUndoTools(host, options);\n}\n\n/**\n * registerUndoTools — add agent_undo / agent_redo / agent_history to the\n * server. Returns a disposer that unregisters all three.\n */\nexport function registerUndoTools(host: ToolHost, options: UndoToolsOptions = {}): () => void {\n  const defaultAgent = options.defaultAgentId ?? \"agent\";\n  const disposers: Array<() => void> = [];\n  const agentOf = (args: any): string =>\n    typeof args?.agentId === \"string\" ? args.agentId : defaultAgent;\n\n  disposers.push(\n    host.registerTool(\n      {\n        name: \"agent_undo\",\n        description: \"Undo the most recent action on the agent's stack. Optional agentId targets a specific agent.\",\n        inputSchema: {\n          type: \"object\",\n          properties: { agentId: { type: \"string\" } },\n          additionalProperties: false,\n        },\n      },\n      async (args) => {\n        const entry = await undoOne(agentOf(args));\n        if (!entry) return errorResult(\"Nothing to undo.\");\n        return textResult(`Undid: ${entry.label}`, { entry: serialize(entry) });\n      },\n      { owner: UNDO_TOOLS_OWNER },\n    ),\n  );\n\n  disposers.push(\n    host.registerTool(\n      {\n        name: \"agent_redo\",\n        description: \"Redo the most recently undone action.\",\n        inputSchema: {\n          type: \"object\",\n          properties: { agentId: { type: \"string\" } },\n          additionalProperties: false,\n        },\n      },\n      async (args) => {\n        const entry = await redoOne(agentOf(args));\n        if (!entry) return errorResult(\"Nothing to redo.\");\n        return textResult(`Redid: ${entry.label}`, { entry: serialize(entry) });\n      },\n      { owner: UNDO_TOOLS_OWNER },\n    ),\n  );\n\n  disposers.push(\n    host.registerTool(\n      {\n        name: \"agent_history\",\n        description: \"List the agent's undo stack (oldest first). Useful for understanding what's reversible.\",\n        inputSchema: {\n          type: \"object\",\n          properties: { agentId: { type: \"string\" } },\n          additionalProperties: false,\n        },\n      },\n      async (args) => {\n        const history = readHistory(agentOf(args)).map(serialize);\n        const text = history.map((e) => `${new Date(e.timestamp).toISOString()} ${e.bridgeId} ${e.action}: ${e.label}`).join(\"\\n\");\n        return textResult(text || \"(empty)\", history);\n      },\n      { owner: UNDO_TOOLS_OWNER },\n    ),\n  );\n\n  return () => disposers.forEach((d) => d());\n}\n\nfunction serialize(entry: import(\"./undo-stack\").UndoEntry) {\n  return {\n    timestamp: entry.timestamp,\n    bridgeId: entry.bridgeId,\n    action: entry.action,\n    label: entry.label,\n  };\n}\n","import { useState, useEffect, useCallback } from \"react\";\nimport { readHistory } from \"./undo-stack\";\n\n/**\n * useUndoStack — minimal React snapshot of an agent's history. Polls every\n * `intervalMs` (default 500). Use this to render an inline \"agent timeline\"\n * in a sidebar or activity panel. No subscription model in v1 — keeping it\n * simple; bridge mutations are infrequent enough that polling is fine.\n */\nexport function useUndoStack(agentId: string, intervalMs = 500) {\n  const [history, setHistory] = useState(() => readHistory(agentId));\n\n  useEffect(() => {\n    let cancelled = false;\n    const tick = () => {\n      if (cancelled) return;\n      setHistory(readHistory(agentId));\n    };\n    const id = setInterval(tick, intervalMs);\n    tick();\n    return () => { cancelled = true; clearInterval(id); };\n  }, [agentId, intervalMs]);\n\n  const refresh = useCallback(() => setHistory(readHistory(agentId)), [agentId]);\n  return { history, refresh };\n}\n"]}