bridge-agent 0.17.0 → 0.18.0

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.
@@ -1299,21 +1299,21 @@ var require_errors = __commonJS({
1299
1299
  function extendErrors({ gen, keyword, schemaValue, data, errsCount, it }) {
1300
1300
  if (errsCount === void 0)
1301
1301
  throw new Error("ajv implementation error");
1302
- const err5 = gen.name("err");
1302
+ const err6 = gen.name("err");
1303
1303
  gen.forRange("i", errsCount, names_1.default.errors, (i) => {
1304
- gen.const(err5, (0, codegen_1._)`${names_1.default.vErrors}[${i}]`);
1305
- gen.if((0, codegen_1._)`${err5}.instancePath === undefined`, () => gen.assign((0, codegen_1._)`${err5}.instancePath`, (0, codegen_1.strConcat)(names_1.default.instancePath, it.errorPath)));
1306
- gen.assign((0, codegen_1._)`${err5}.schemaPath`, (0, codegen_1.str)`${it.errSchemaPath}/${keyword}`);
1304
+ gen.const(err6, (0, codegen_1._)`${names_1.default.vErrors}[${i}]`);
1305
+ gen.if((0, codegen_1._)`${err6}.instancePath === undefined`, () => gen.assign((0, codegen_1._)`${err6}.instancePath`, (0, codegen_1.strConcat)(names_1.default.instancePath, it.errorPath)));
1306
+ gen.assign((0, codegen_1._)`${err6}.schemaPath`, (0, codegen_1.str)`${it.errSchemaPath}/${keyword}`);
1307
1307
  if (it.opts.verbose) {
1308
- gen.assign((0, codegen_1._)`${err5}.schema`, schemaValue);
1309
- gen.assign((0, codegen_1._)`${err5}.data`, data);
1308
+ gen.assign((0, codegen_1._)`${err6}.schema`, schemaValue);
1309
+ gen.assign((0, codegen_1._)`${err6}.data`, data);
1310
1310
  }
1311
1311
  });
1312
1312
  }
1313
1313
  exports2.extendErrors = extendErrors;
1314
1314
  function addError(gen, errObj) {
1315
- const err5 = gen.const("err", errObj);
1316
- gen.if((0, codegen_1._)`${names_1.default.vErrors} === null`, () => gen.assign(names_1.default.vErrors, (0, codegen_1._)`[${err5}]`), (0, codegen_1._)`${names_1.default.vErrors}.push(${err5})`);
1315
+ const err6 = gen.const("err", errObj);
1316
+ gen.if((0, codegen_1._)`${names_1.default.vErrors} === null`, () => gen.assign(names_1.default.vErrors, (0, codegen_1._)`[${err6}]`), (0, codegen_1._)`${names_1.default.vErrors}.push(${err6})`);
1317
1317
  gen.code((0, codegen_1._)`${names_1.default.errors}++`);
1318
1318
  }
1319
1319
  function returnErrors(it, errs) {
@@ -7541,8 +7541,8 @@ var ZodType = class {
7541
7541
  } : {
7542
7542
  issues: ctx.common.issues
7543
7543
  };
7544
- } catch (err5) {
7545
- if (err5?.message?.toLowerCase()?.includes("encountered")) {
7544
+ } catch (err6) {
7545
+ if (err6?.message?.toLowerCase()?.includes("encountered")) {
7546
7546
  this["~standard"].async = true;
7547
7547
  }
7548
7548
  ctx.common = {
@@ -21243,16 +21243,16 @@ var handleFetchError = (e) => new Response(null, {
21243
21243
  status: e instanceof Error && (e.name === "TimeoutError" || e.constructor.name === "TimeoutError") ? 504 : 500
21244
21244
  });
21245
21245
  var handleResponseError = (e, outgoing) => {
21246
- const err5 = e instanceof Error ? e : new Error("unknown error", { cause: e });
21247
- if (err5.code === "ERR_STREAM_PREMATURE_CLOSE") {
21246
+ const err6 = e instanceof Error ? e : new Error("unknown error", { cause: e });
21247
+ if (err6.code === "ERR_STREAM_PREMATURE_CLOSE") {
21248
21248
  console.info("The user aborted a request.");
21249
21249
  } else {
21250
21250
  console.error(e);
21251
21251
  if (!outgoing.headersSent) {
21252
21252
  outgoing.writeHead(500, { "Content-Type": "text/plain" });
21253
21253
  }
21254
- outgoing.end(`Error: ${err5.message}`);
21255
- outgoing.destroy(err5);
21254
+ outgoing.end(`Error: ${err6.message}`);
21255
+ outgoing.destroy(err6);
21256
21256
  }
21257
21257
  };
21258
21258
  var flushHeaders = (outgoing) => {
@@ -21309,8 +21309,8 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
21309
21309
  if (options.errorHandler) {
21310
21310
  try {
21311
21311
  res = await res;
21312
- } catch (err5) {
21313
- const errRes = await options.errorHandler(err5);
21312
+ } catch (err6) {
21313
+ const errRes = await options.errorHandler(err6);
21314
21314
  if (!errRes) {
21315
21315
  return;
21316
21316
  }
@@ -22300,19 +22300,20 @@ async function request(ctx, method, url, body, opts) {
22300
22300
  throw new Error(message || `HTTP ${res.status}`);
22301
22301
  }
22302
22302
  return res.json();
22303
- } catch (err5) {
22303
+ } catch (err6) {
22304
22304
  clearTimeout(timer);
22305
- if (err5 instanceof Error && err5.name === "AbortError") {
22305
+ if (err6 instanceof Error && err6.name === "AbortError") {
22306
22306
  throw new Error(`Request timed out after ${timeoutMs}ms: ${method} ${url}`);
22307
22307
  }
22308
- throw err5;
22308
+ throw err6;
22309
22309
  }
22310
22310
  }
22311
22311
  async function getProject(ctx) {
22312
22312
  return request(ctx, "GET", projectPath(ctx));
22313
22313
  }
22314
22314
  async function updateProject(ctx, description) {
22315
- return request(ctx, "PATCH", projectPath(ctx), { description });
22315
+ const res = await request(ctx, "PATCH", projectPath(ctx), { description });
22316
+ return res.project;
22316
22317
  }
22317
22318
  async function getTodos(ctx, scope) {
22318
22319
  const qs = scope === "active" ? "?scope=active" : "";
@@ -22392,9 +22393,12 @@ async function killAgent(ctx, agentId) {
22392
22393
  async function assignTask(ctx, todoId, agentId) {
22393
22394
  return request(ctx, "POST", projectPath(ctx, `/todos/${todoId}/assign`), { agentId });
22394
22395
  }
22395
- async function getAgentOutput(ctx, agentId, lines) {
22396
- const qs = lines !== void 0 ? `?lines=${lines}` : "";
22397
- return request(ctx, "GET", projectPath(ctx, `/panels/${agentId}/output${qs}`));
22396
+ async function getAgentOutput(ctx, agentId, lines, cursor) {
22397
+ const params = new URLSearchParams();
22398
+ if (lines !== void 0) params.set("lines", String(lines));
22399
+ if (cursor) params.set("cursor", cursor);
22400
+ const qs = params.toString();
22401
+ return request(ctx, "GET", projectPath(ctx, `/panels/${agentId}/output${qs ? `?${qs}` : ""}`));
22398
22402
  }
22399
22403
  async function sendAgentInput(ctx, agentId, text) {
22400
22404
  return request(ctx, "POST", workspacePath(ctx, `/agents/${agentId}/input`), { text });
@@ -22533,9 +22537,37 @@ function registerPlanTools(server, ctx) {
22533
22537
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
22534
22538
  }
22535
22539
  );
22540
+ server.tool(
22541
+ "bridge_get_blueprint",
22542
+ "Get the project blueprint \u2014 the architecture/conventions/features/decisions markdown doc for this project. Returns { blueprint, updatedAt }; updatedAt is null when the blueprint was never seeded.",
22543
+ {},
22544
+ async () => {
22545
+ const project = await getProject(ctx);
22546
+ return {
22547
+ content: [{
22548
+ type: "text",
22549
+ text: JSON.stringify({ blueprint: project.description ?? "", updatedAt: project.blueprintUpdatedAt ?? null })
22550
+ }]
22551
+ };
22552
+ }
22553
+ );
22554
+ server.tool(
22555
+ "bridge_update_blueprint",
22556
+ "Update the project blueprint markdown doc. Stored as the project description and immediately reflected in the Bridge UI. Returns { ok, blueprint, updatedAt }.",
22557
+ { content: external_exports.string().describe("New blueprint content in Markdown") },
22558
+ async ({ content }) => {
22559
+ const project = await updateProject(ctx, content);
22560
+ return {
22561
+ content: [{
22562
+ type: "text",
22563
+ text: JSON.stringify({ ok: true, blueprint: project.description ?? "", updatedAt: project.blueprintUpdatedAt ?? null })
22564
+ }]
22565
+ };
22566
+ }
22567
+ );
22536
22568
  server.tool(
22537
22569
  "bridge_get_plan",
22538
- "Get the current plan / spec for this Bridge project. Returns the markdown content stored in the project description field.",
22570
+ "DEPRECATED \u2014 use bridge_get_blueprint. Get the current plan / spec for this Bridge project (legacy { plan } response shape).",
22539
22571
  {},
22540
22572
  async () => {
22541
22573
  const project = await getProject(ctx);
@@ -22549,14 +22581,14 @@ function registerPlanTools(server, ctx) {
22549
22581
  );
22550
22582
  server.tool(
22551
22583
  "bridge_update_plan",
22552
- "Update the plan / spec for this Bridge project. The content will be stored as the project description and immediately reflected in the Bridge UI.",
22584
+ "DEPRECATED \u2014 use bridge_update_blueprint. Update the plan / spec for this Bridge project (legacy response shape).",
22553
22585
  { content: external_exports.string().describe("New plan content in Markdown") },
22554
22586
  async ({ content }) => {
22555
- const result = await updateProject(ctx, content);
22587
+ await updateProject(ctx, content);
22556
22588
  return {
22557
22589
  content: [{
22558
22590
  type: "text",
22559
- text: JSON.stringify(result)
22591
+ text: JSON.stringify({ ok: true })
22560
22592
  }]
22561
22593
  };
22562
22594
  }
@@ -22672,65 +22704,84 @@ var EVENT_ROLES = [...AGENT_ROLES, "system"];
22672
22704
  // ../shared/dist/tool-registry.js
22673
22705
  var BRIDGE_TOOL_DOCS = {
22674
22706
  // Project / plan
22675
- bridge_get_project: "Project metadata: name, cwd, machineId",
22676
- bridge_get_plan: "Read project spec/description",
22677
- bridge_update_plan: "Update project spec/description",
22678
- bridge_get_project_history: "Past run history and failure patterns",
22679
- bridge_get_execution_status: "Run history with todo completion counts",
22707
+ bridge_get_project: "Project metadata (name, cwd, machineId)",
22708
+ bridge_get_blueprint: "Read project blueprint doc ({blueprint, updatedAt})",
22709
+ bridge_update_blueprint: "Update project blueprint doc",
22710
+ bridge_get_project_history: "Past runs + failure patterns",
22711
+ bridge_get_execution_status: "Run history + todo completion counts",
22680
22712
  // Todos
22681
- bridge_get_todos: "List todos + session state for this project",
22682
- bridge_add_todo: "Create a new todo (title, todoType, dependsOn)",
22683
- bridge_update_todo: "Update a todo title or status",
22684
- bridge_cancel_run: "Cancel active run (use before restarting a stale plan)",
22713
+ bridge_get_todos: "List todos + session state",
22714
+ bridge_add_todo: "Create todo (title, todoType, dependsOn)",
22715
+ bridge_update_todo: "Update todo title/status",
22716
+ bridge_cancel_run: "Cancel active run (before restarting a stale plan)",
22685
22717
  // Panel management
22686
- bridge_list_agents: "All agents: role, status, inRun flag",
22687
- bridge_get_agent_status: "Single agent status check",
22688
- bridge_spawn_worker: "Spawn a new worker agent (agentKey, role)",
22689
- bridge_kill_agent: "Terminate a stuck or dead agent",
22690
- bridge_get_agent_output: "Read terminal output of any agent",
22691
- bridge_send_input: "Send text input to an agent PTY",
22718
+ bridge_list_agents: "All agents: role, status, inRun",
22719
+ bridge_get_agent_status: "Single agent status",
22720
+ bridge_spawn_worker: "Spawn worker (agentKey, role)",
22721
+ bridge_kill_agent: "Terminate stuck/dead agent",
22722
+ bridge_get_agent_output: "Read agent terminal output (round-trip cursor = delta read)",
22723
+ bridge_send_input: "Send text input to agent PTY",
22692
22724
  // Worker task lifecycle
22693
- bridge_get_my_task: "Get the task assigned to this agent",
22725
+ bridge_get_my_task: "This agent's assigned task",
22694
22726
  bridge_complete_task: "Signal task completion",
22695
- bridge_fail_task: "Signal task failure with a specific reason",
22696
- bridge_get_todo_context: "Read todo output/error for a specific todo",
22697
- bridge_assign_task: "Assign a pending todo to a specific agent",
22727
+ bridge_fail_task: "Signal failure with reason",
22728
+ bridge_get_todo_context: "Read a todo's output/error",
22729
+ bridge_assign_task: "Assign pending todo to an agent",
22698
22730
  // Workspace scope (Phase 6)
22699
- bridge_list_groups: "List all agent groups and their member agentIds",
22700
- bridge_get_group_status: "Get agents in a group with status and last output line",
22701
- bridge_dispatch_to_group: "Send a text message/command to every agent in a group",
22702
- bridge_list_workspace_projects: "List all workspace projects with name, cwd, active run count",
22703
- bridge_query_workspace: "Natural-language query about orchestration state",
22704
- bridge_list_active_runs: "All active orchestration runs across workspace projects",
22705
- bridge_peek_panel: "Read terminal output of any panel workspace-wide (no project boundary)",
22706
- bridge_status_panel: "Get status of any panel in the workspace regardless of project",
22731
+ bridge_list_groups: "Groups + member agentIds",
22732
+ bridge_get_group_status: "Group agents: status + last output line",
22733
+ bridge_dispatch_to_group: "Send message to every agent in a group",
22734
+ bridge_create_group: "Create group (name, optional color)",
22735
+ bridge_update_group: "Rename/recolor a group",
22736
+ bridge_delete_group: "Delete a group",
22737
+ bridge_assign_agent_to_group: "Move agent into a group, or remove (groupId: null); orchestrators cannot join",
22738
+ bridge_list_workspace_projects: "Workspace projects: name, cwd, active run count",
22739
+ bridge_query_workspace: "Natural-language query of orchestration state",
22740
+ bridge_list_active_runs: "Active runs across workspace projects",
22741
+ bridge_peek_panel: "Read any panel output workspace-wide (cursor = delta read)",
22742
+ bridge_status_panel: "Status of any panel workspace-wide",
22707
22743
  // Personas
22708
- bridge_get_persona: "Fetch a persona by id (name, systemPrompt, role, agentKey)",
22709
- bridge_list_personas: "List all personas visible to the caller (workspace + personal)",
22710
- bridge_create_persona: "Create a custom persona definition",
22711
- bridge_update_persona: "Update persona definition fields",
22712
- bridge_archive_persona: "Archive an outdated persona",
22713
- bridge_launch_persona: "Spawn a worker initialized with a specific persona",
22714
- bridge_apply_persona: "Apply persona configuration to an existing running worker",
22744
+ bridge_get_persona: "Persona by id (name, systemPrompt, role, agentKey)",
22745
+ bridge_list_personas: "Visible personas (workspace + personal)",
22746
+ bridge_create_persona: "Create persona",
22747
+ bridge_update_persona: "Update persona fields",
22748
+ bridge_archive_persona: "Archive a persona",
22749
+ bridge_launch_persona: "Spawn worker with a persona",
22750
+ bridge_apply_persona: "Apply persona to a running worker",
22751
+ // Group Schemas (reusable team templates)
22752
+ bridge_list_group_schemas: "Reusable team/group schema templates",
22753
+ bridge_create_group_schema: "Create group schema from current groups",
22754
+ bridge_update_group_schema: "Update a group schema (name/slug/desc/color/groups/shared)",
22755
+ bridge_apply_group_schema: "Spawn a schema's groups and agents",
22756
+ // Agent Model Info
22757
+ bridge_list_agent_models: "Available models for an agent key on a daemon",
22715
22758
  // Messaging
22716
- bridge_send_message: "Send direct message to peer agent conversation",
22717
- bridge_poll_messages: "Poll unread messages delivered to this agent",
22759
+ bridge_send_message: "Direct message to a peer agent",
22760
+ bridge_poll_messages: "Poll unread messages for this agent",
22718
22761
  // Role Prompts
22719
- bridge_list_role_prompts: "List available system role prompt templates",
22720
- bridge_get_role_prompt: "Fetch resolved system role prompt template",
22721
- bridge_update_role_prompt: "Update custom role prompt template",
22722
- bridge_delete_role_prompt: "Delete custom role prompt template",
22762
+ bridge_list_role_prompts: "Role prompt templates",
22763
+ bridge_get_role_prompt: "Fetch resolved role prompt",
22764
+ bridge_update_role_prompt: "Update custom role prompt",
22765
+ bridge_delete_role_prompt: "Delete custom role prompt",
22723
22766
  // Events & Memory
22724
- bridge_agent_is_idle: "Check if agent terminal PTY is idle and ready for command injection",
22725
- bridge_record_event: "Record an event, constraint, or blocker for orchestration memory",
22726
- bridge_get_project_events: "Fetch recorded project events filtered by tags or permanence",
22727
- bridge_get_project_memory: "Fetch durable permanent guardrails and architectural constraints",
22728
- bridge_update_todo_status: "Update todo status directly without modifying title"
22767
+ bridge_agent_is_idle: "Is agent PTY idle (ready for injection)",
22768
+ bridge_record_event: "Record event/constraint/blocker to orchestration memory",
22769
+ bridge_get_project_events: "Project events filtered by tags/permanence",
22770
+ bridge_get_project_memory: "Durable guardrails + constraints",
22771
+ bridge_update_todo_status: "Update todo status only"
22729
22772
  };
22730
22773
  if (Object.keys(BRIDGE_TOOL_DOCS).length === 0) {
22731
22774
  throw new Error("BRIDGE_TOOL_DOCS registry is empty at module load \u2014 fail fast");
22732
22775
  }
22733
22776
 
22777
+ // ../shared/dist/blueprint.js
22778
+ var BLUEPRINT_H1_AREAS = ["Frontend", "Backend", "Other"];
22779
+ var BLUEPRINT_H2_ASPECTS = ["Architecture", "Conventions", "Features", "Decisions"];
22780
+ var BLUEPRINT_DOC_TEMPLATE = BLUEPRINT_H1_AREAS.map((area) => `# ${area}
22781
+
22782
+ ` + BLUEPRINT_H2_ASPECTS.map((aspect) => `## ${aspect}
22783
+ `).join("\n")).join("\n");
22784
+
22734
22785
  // ../mcp-server/src/tools/orchestration.ts
22735
22786
  function ok(data) {
22736
22787
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
@@ -22815,12 +22866,13 @@ function registerOrchestrationTools(server, ctx) {
22815
22866
  );
22816
22867
  server.tool(
22817
22868
  "bridge_get_agent_output",
22818
- "Read the last N lines of a panel's terminal output (ANSI codes stripped). Use this to check a dev server for errors, verify a build succeeded, or read a shell panel's result. Works on any agent type \u2014 dev servers, shells, AI agents.",
22869
+ "Read the last N lines of a panel's terminal output (ANSI codes stripped). Returns { output, cursor, cursor_reset }. Round-trip the returned cursor on the next call to get only new output (delta) \u2014 omit it for a full tail. cursor_reset: true means the cursor went stale (buffer evicted or panel respawned) and a full tail was returned. Use this to check a dev server for errors, verify a build succeeded, or read a shell panel's result. Works on any agent type \u2014 dev servers, shells, AI agents.",
22819
22870
  {
22820
22871
  agentId: external_exports.string().describe("The agent ID to read"),
22821
- lines: external_exports.number().int().min(1).max(300).optional().describe("Trailing lines to return (default 50, max 300)")
22872
+ lines: external_exports.number().int().min(1).max(300).optional().describe("Trailing lines for full-tail reads (default 50, max 300); delta reads return all new lines"),
22873
+ cursor: external_exports.string().optional().describe("Opaque cursor from a previous response \u2014 pass it back to receive only new output since that read")
22822
22874
  },
22823
- ({ agentId, lines }) => safe(() => getAgentOutput(ctx, agentId, lines))
22875
+ ({ agentId, lines, cursor }) => safe(() => getAgentOutput(ctx, agentId, lines, cursor))
22824
22876
  );
22825
22877
  server.tool(
22826
22878
  "bridge_send_input",
@@ -23000,12 +23052,19 @@ function registerWorkspaceTools(server, ctx) {
23000
23052
  );
23001
23053
  server.tool(
23002
23054
  "bridge_peek_panel",
23003
- "Read the last N lines of a panel's terminal output. Project-scoped contexts may only peek at their own project's panels (403 cross_project_block otherwise); workspace scope has no project boundary. Use this to check what a specific agent is doing.",
23055
+ "Read the last N lines of a panel's terminal output. Project-scoped contexts may only peek at their own project's panels (403 cross_project_block otherwise); workspace scope has no project boundary. Use this to check what a specific agent is doing. Returns { output, cursor, cursor_reset } \u2014 round-trip the cursor on the next call to get only new output (delta); omit it for a full tail.",
23004
23056
  {
23005
23057
  agentId: external_exports.string().describe("The panel/agent ID to peek at"),
23006
- lines: external_exports.number().int().min(1).max(300).optional().describe("Trailing lines to return (default 50, max 300)")
23058
+ lines: external_exports.number().int().min(1).max(300).optional().describe("Trailing lines for full-tail reads (default 50, max 300); delta reads return all new lines"),
23059
+ cursor: external_exports.string().optional().describe("Opaque cursor from a previous response \u2014 pass it back to receive only new output since that read")
23007
23060
  },
23008
- ({ agentId, lines }) => safe2(() => request(ctx, "GET", workspacePath(ctx, `/agents/${agentId}/output${lines !== void 0 ? `?lines=${lines}` : ""}`)))
23061
+ ({ agentId, lines, cursor }) => safe2(() => {
23062
+ const params = new URLSearchParams();
23063
+ if (lines !== void 0) params.set("lines", String(lines));
23064
+ if (cursor) params.set("cursor", cursor);
23065
+ const qs = params.toString();
23066
+ return request(ctx, "GET", workspacePath(ctx, `/agents/${agentId}/output${qs ? `?${qs}` : ""}`));
23067
+ })
23009
23068
  );
23010
23069
  server.tool(
23011
23070
  "bridge_status_panel",
@@ -23013,6 +23072,49 @@ function registerWorkspaceTools(server, ctx) {
23013
23072
  { agentId: external_exports.string().describe("The panel/agent ID to inspect") },
23014
23073
  ({ agentId }) => safe2(() => request(ctx, "GET", workspacePath(ctx, `/agents/${agentId}`)))
23015
23074
  );
23075
+ server.tool(
23076
+ "bridge_create_group",
23077
+ "Create a new agent group in the current project with an optional color.",
23078
+ {
23079
+ name: external_exports.string().min(1).max(64).describe("Human-readable group name"),
23080
+ color: external_exports.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("Hex color e.g. #6366f1")
23081
+ },
23082
+ ({ name, color }) => safe2(() => {
23083
+ if (!ctx.projectId) throw new Error("Project-scoped context required");
23084
+ return request(ctx, "POST", workspacePath(ctx, `/projects/${ctx.projectId}/groups`), { name, color });
23085
+ })
23086
+ );
23087
+ server.tool(
23088
+ "bridge_update_group",
23089
+ "Rename or recolor an existing agent group.",
23090
+ {
23091
+ groupId: external_exports.string().describe("The group ID to update"),
23092
+ name: external_exports.string().min(1).max(64).optional().describe("New group name"),
23093
+ color: external_exports.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("New hex color e.g. #6366f1")
23094
+ },
23095
+ ({ groupId, name, color }) => safe2(() => {
23096
+ if (!ctx.projectId) throw new Error("Project-scoped context required");
23097
+ return request(ctx, "PATCH", workspacePath(ctx, `/projects/${ctx.projectId}/groups/${groupId}`), { name, color });
23098
+ })
23099
+ );
23100
+ server.tool(
23101
+ "bridge_assign_agent_to_group",
23102
+ "Assign a running agent to a group (team), or remove it with groupId:null. Orchestrator panels cannot join groups. Project-scoped context required; the group must already exist (use bridge_create_group first).",
23103
+ { agentId: external_exports.string().min(1), groupId: external_exports.string().nullable() },
23104
+ ({ agentId, groupId }) => safe2(() => {
23105
+ if (!ctx.projectId) throw new Error("Project-scoped context required");
23106
+ return request(ctx, "PATCH", workspacePath(ctx, `/projects/${ctx.projectId}/agents/${agentId}/group`), { groupId });
23107
+ })
23108
+ );
23109
+ server.tool(
23110
+ "bridge_delete_group",
23111
+ "Delete an agent group. Members are detached but keep running.",
23112
+ { groupId: external_exports.string().describe("The group ID to delete") },
23113
+ ({ groupId }) => safe2(() => {
23114
+ if (!ctx.projectId) throw new Error("Project-scoped context required");
23115
+ return request(ctx, "DELETE", workspacePath(ctx, `/projects/${ctx.projectId}/groups/${groupId}`));
23116
+ })
23117
+ );
23016
23118
  }
23017
23119
 
23018
23120
  // ../mcp-server/src/tools/personas.ts
@@ -23188,6 +23290,123 @@ function registerRolePromptTools(server, ctx) {
23188
23290
  );
23189
23291
  }
23190
23292
 
23293
+ // ../mcp-server/src/tools/group-schemas.ts
23294
+ function ok5(data) {
23295
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
23296
+ }
23297
+ function err5(message) {
23298
+ return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: message }) }] };
23299
+ }
23300
+ async function safe5(fn) {
23301
+ try {
23302
+ return ok5(await fn());
23303
+ } catch (e) {
23304
+ const message = e instanceof Error ? e.message : String(e);
23305
+ if (e instanceof Error && (e.name === "TypeError" || e.name === "ReferenceError")) {
23306
+ console.error("[mcp-bridge] unexpected error in group-schema tool handler", { name: e.name, message, stack: e.stack });
23307
+ }
23308
+ return err5(message);
23309
+ }
23310
+ }
23311
+ function registerGroupSchemaTools(server, ctx) {
23312
+ server.tool(
23313
+ "bridge_list_group_schemas",
23314
+ "List reusable team/group schema templates saved in this workspace. Returns name, slug, color, groupsJson payload, and metadata for each schema.",
23315
+ {
23316
+ scope: external_exports.enum(["personal", "workspace", "project"]).optional().describe("Scope filter"),
23317
+ projectId: external_exports.string().optional().describe("Filter to a specific project"),
23318
+ q: external_exports.string().optional().describe("Substring search on name")
23319
+ },
23320
+ ({ scope, projectId, q }) => safe5(() => {
23321
+ let path = "/group-schemas";
23322
+ const params = new URLSearchParams();
23323
+ if (scope) params.set("scope", scope);
23324
+ if (projectId) params.set("projectId", projectId);
23325
+ if (q) params.set("q", q);
23326
+ const qs = params.toString();
23327
+ if (qs) path += "?" + qs;
23328
+ return request(ctx, "GET", workspacePath(ctx, path));
23329
+ })
23330
+ );
23331
+ server.tool(
23332
+ "bridge_apply_group_schema",
23333
+ "Apply a group schema template to spawn all its groups and agents in one call. Validates model availability against the target daemon. Returns created groups + agent IDs and any partial failures.",
23334
+ {
23335
+ schemaId: external_exports.string().describe("The group schema ID to apply"),
23336
+ projectId: external_exports.string().describe("Target project ID"),
23337
+ daemonId: external_exports.string().optional().describe("Target daemon ID (auto-resolves if omitted)"),
23338
+ mode: external_exports.enum(["auto-suffix", "replace"]).optional().describe("Name collision mode: auto-suffix (default) appends -2, -3, etc; replace reaps existing teams first")
23339
+ },
23340
+ ({ schemaId, projectId, daemonId, mode }) => safe5(
23341
+ () => request(
23342
+ ctx,
23343
+ "POST",
23344
+ workspacePath(ctx, `/group-schemas/${schemaId}/apply`),
23345
+ { projectId, daemonId, mode }
23346
+ )
23347
+ )
23348
+ );
23349
+ server.tool(
23350
+ "bridge_create_group_schema",
23351
+ "Create a reusable group schema template from the current project group setup. Serializes groups and agent slots into a template that can be applied later with bridge_apply_group_schema.",
23352
+ {
23353
+ name: external_exports.string().min(1).max(80).describe("Display name for the schema"),
23354
+ slug: external_exports.string().regex(/^[a-z][a-z0-9_]{1,62}$/).describe("Unique slug (lowercase, starts with letter, a-z0-9_ only, 2-63 chars)"),
23355
+ description: external_exports.string().max(200).optional().describe("Optional description"),
23356
+ color: external_exports.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("Hex colour for the schema card"),
23357
+ groupsJson: external_exports.any().optional().describe("GroupSchemaPayload: { version:1, groups:[{name, color?, slots:[{agentKey, model?, role?, count, cmd?}]}] }"),
23358
+ scope: external_exports.enum(["personal", "workspace", "project"]).optional().describe("Visibility scope (default: workspace)"),
23359
+ projectId: external_exports.string().optional().describe("Project ID (required if scope=project)")
23360
+ },
23361
+ ({ name, slug, description, color, groupsJson, scope, projectId }) => safe5(
23362
+ () => request(
23363
+ ctx,
23364
+ "POST",
23365
+ workspacePath(ctx, "/group-schemas"),
23366
+ { name, slug, description, color, groupsJson, scope, projectId }
23367
+ )
23368
+ )
23369
+ );
23370
+ server.tool(
23371
+ "bridge_update_group_schema",
23372
+ "Update an existing (non-archived) group schema template. Pass schemaId plus only the fields to change (name, slug, description, color, groupsJson, shared). Returns the updated schema.",
23373
+ {
23374
+ schemaId: external_exports.string().describe("The group schema ID to update"),
23375
+ name: external_exports.string().min(1).max(80).optional().describe("New display name"),
23376
+ slug: external_exports.string().regex(/^[a-z][a-z0-9_]{1,62}$/).optional().describe("New unique slug (lowercase, starts with letter, a-z0-9_, 2-63 chars)"),
23377
+ description: external_exports.string().max(200).optional().describe("New description"),
23378
+ color: external_exports.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("New hex colour e.g. #6366f1"),
23379
+ groupsJson: external_exports.any().optional().describe("Replacement GroupSchemaPayload: { version:1, groups:[{name, color?, slots:[{agentKey, model?, role?, count, cmd?}]}] }"),
23380
+ shared: external_exports.boolean().optional().describe("Workspace-scope only: mark the schema shared across the workspace")
23381
+ },
23382
+ ({ schemaId, name, slug, description, color, groupsJson, shared }) => safe5(
23383
+ () => request(
23384
+ ctx,
23385
+ "PATCH",
23386
+ workspacePath(ctx, `/group-schemas/${schemaId}`),
23387
+ { name, slug, description, color, groupsJson, shared }
23388
+ )
23389
+ )
23390
+ );
23391
+ server.tool(
23392
+ "bridge_list_agent_models",
23393
+ "List available models for an agent key on a target daemon. Validates model portability across machines; use before applying a group schema to verify required models exist.",
23394
+ {
23395
+ agentKey: external_exports.string().optional().describe("Filter by agent key (e.g. claude, kimi, qwen)"),
23396
+ daemonId: external_exports.string().optional().describe("Filter by daemon ID")
23397
+ },
23398
+ ({ agentKey, daemonId }) => safe5(() => {
23399
+ let path = "/agent-models";
23400
+ const params = new URLSearchParams();
23401
+ if (agentKey) params.set("agentKey", agentKey);
23402
+ if (daemonId) params.set("daemonId", daemonId);
23403
+ const qs = params.toString();
23404
+ if (qs) path += "?" + qs;
23405
+ return request(ctx, "GET", workspacePath(ctx, path));
23406
+ })
23407
+ );
23408
+ }
23409
+
23191
23410
  // ../mcp-server/src/index.ts
23192
23411
  var sessions = /* @__PURE__ */ new Map();
23193
23412
  function buildMcpServer(ctx) {
@@ -23199,6 +23418,7 @@ function buildMcpServer(ctx) {
23199
23418
  registerWorkspaceTools(srv, ctx);
23200
23419
  registerPersonaTools(srv, ctx);
23201
23420
  registerRolePromptTools(srv, ctx);
23421
+ registerGroupSchemaTools(srv, ctx);
23202
23422
  return srv;
23203
23423
  }
23204
23424
  async function parseBody(req) {
@@ -23303,8 +23523,8 @@ function startHttpServer() {
23303
23523
  const mcpServer = buildMcpServer(ctx);
23304
23524
  await mcpServer.connect(transport);
23305
23525
  await transport.handleRequest(req, res, body);
23306
- } catch (err5) {
23307
- console.error("[bridge-mcp] request error:", err5);
23526
+ } catch (err6) {
23527
+ console.error("[bridge-mcp] request error:", err6);
23308
23528
  if (!res.headersSent) {
23309
23529
  res.writeHead(500, { "Content-Type": "application/json" });
23310
23530
  res.end(JSON.stringify({ error: "Internal server error" }));
@@ -23339,8 +23559,8 @@ async function startStdioServer() {
23339
23559
  if (process.env["HTTP_MODE"] !== "false") {
23340
23560
  startHttpServer();
23341
23561
  } else {
23342
- startStdioServer().catch((err5) => {
23343
- console.error("[bridge-mcp] fatal:", err5);
23562
+ startStdioServer().catch((err6) => {
23563
+ console.error("[bridge-mcp] fatal:", err6);
23344
23564
  process.exit(1);
23345
23565
  });
23346
23566
  }