opencode-gitlab-dap 1.4.3 → 1.5.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.
package/dist/index.js CHANGED
@@ -3123,6 +3123,7 @@ components:
3123
3123
  prompt_version: "^1.0.0"
3124
3124
  response_schema_id: "fix_pipeline_decide_approach"
3125
3125
  response_schema_version: "^1.0.0"
3126
+ response_schema_tracking: true
3126
3127
  inputs:
3127
3128
  - from: "context:fix_pipeline_context.final_answer"
3128
3129
  as: "fix_pipeline_context_results"
@@ -3467,6 +3468,7 @@ components:
3467
3468
  prompt_version: "^1.0.0"
3468
3469
  response_schema_id: "fix_pipeline_next_decide_approach"
3469
3470
  response_schema_version: "^1.0.0"
3471
+ response_schema_tracking: true
3470
3472
  inputs:
3471
3473
  - from: "context:fix_pipeline_context.final_answer"
3472
3474
  as: "fix_pipeline_context_results"
@@ -3911,6 +3913,61 @@ async function fetchCustomAgents(instanceUrl, token, projectId) {
3911
3913
  }
3912
3914
  return agents;
3913
3915
  }
3916
+ var MCP_SERVERS_QUERY = `
3917
+ query AiCatalogMcpServers($projectId: ProjectID!, $after: String) {
3918
+ aiCatalogConfiguredItems(first: 50, projectId: $projectId, itemTypes: [AGENT], after: $after) {
3919
+ pageInfo { hasNextPage endCursor }
3920
+ nodes {
3921
+ item {
3922
+ id
3923
+ name
3924
+ latestVersion {
3925
+ ... on AiCatalogAgentVersion {
3926
+ mcpServers {
3927
+ nodes { id name description url transport authType currentUserConnected }
3928
+ }
3929
+ }
3930
+ }
3931
+ }
3932
+ }
3933
+ }
3934
+ }`;
3935
+ async function fetchMcpServers(instanceUrl, token, projectId, agents) {
3936
+ try {
3937
+ let after = null;
3938
+ const serversByAgent = /* @__PURE__ */ new Map();
3939
+ for (; ; ) {
3940
+ const data = await gql(instanceUrl, token, MCP_SERVERS_QUERY, {
3941
+ projectId,
3942
+ ...after ? { after } : {}
3943
+ });
3944
+ const page = data?.aiCatalogConfiguredItems;
3945
+ if (!page) break;
3946
+ for (const node of page.nodes ?? []) {
3947
+ const item = node.item;
3948
+ const version = item?.latestVersion;
3949
+ if (!version?.mcpServers?.nodes?.length) continue;
3950
+ const servers = version.mcpServers.nodes.map((s) => ({
3951
+ id: s.id,
3952
+ name: s.name,
3953
+ description: s.description ?? "",
3954
+ url: s.url,
3955
+ transport: s.transport,
3956
+ authType: s.authType,
3957
+ currentUserConnected: !!s.currentUserConnected
3958
+ }));
3959
+ serversByAgent.set(item.id, servers);
3960
+ }
3961
+ if (!page.pageInfo?.hasNextPage) break;
3962
+ after = page.pageInfo.endCursor;
3963
+ }
3964
+ for (const agent of agents) {
3965
+ const servers = serversByAgent.get(agent.identifier);
3966
+ if (servers?.length) agent.mcpServers = servers;
3967
+ }
3968
+ } catch {
3969
+ }
3970
+ }
3914
3971
  async function getFlowDefinition(instanceUrl, token, opts) {
3915
3972
  const name = opts.flowName ?? `consumer-${opts.consumerId}`;
3916
3973
  let config = null;
@@ -4056,7 +4113,9 @@ async function fetchCatalogAgents(instanceUrl, token, projectId) {
4056
4113
  fetchFoundationalChatAgents(instanceUrl, token, projectId),
4057
4114
  fetchCustomAgents(instanceUrl, token, projectId)
4058
4115
  ]);
4059
- return [...foundational, ...custom];
4116
+ const agents = [...foundational, ...custom];
4117
+ await fetchMcpServers(instanceUrl, token, projectId, agents);
4118
+ return agents;
4060
4119
  } catch {
4061
4120
  return [];
4062
4121
  }
@@ -4195,6 +4254,67 @@ query resolveProjectIds($projectPath: ID!) {
4195
4254
  namespace { id }
4196
4255
  }
4197
4256
  }`;
4257
+ var CREATE_AGENT_MUTATION = `
4258
+ mutation AiCatalogAgentCreate($input: AiCatalogAgentCreateInput!) {
4259
+ aiCatalogAgentCreate(input: $input) {
4260
+ errors
4261
+ item {
4262
+ id
4263
+ name
4264
+ description
4265
+ itemType
4266
+ public
4267
+ project { id, nameWithNamespace, webUrl }
4268
+ latestVersion {
4269
+ id
4270
+ humanVersionName
4271
+ released
4272
+ ... on AiCatalogAgentVersion {
4273
+ systemPrompt
4274
+ userPrompt
4275
+ tools { nodes { id, name, description } }
4276
+ mcpServers { nodes { id, name, url } }
4277
+ }
4278
+ }
4279
+ }
4280
+ }
4281
+ }`;
4282
+ var UPDATE_AGENT_MUTATION = `
4283
+ mutation AiCatalogAgentUpdate($input: AiCatalogAgentUpdateInput!) {
4284
+ aiCatalogAgentUpdate(input: $input) {
4285
+ errors
4286
+ item {
4287
+ id
4288
+ name
4289
+ description
4290
+ itemType
4291
+ public
4292
+ project { id, nameWithNamespace, webUrl }
4293
+ latestVersion {
4294
+ id
4295
+ humanVersionName
4296
+ released
4297
+ ... on AiCatalogAgentVersion {
4298
+ systemPrompt
4299
+ userPrompt
4300
+ tools { nodes { id, name, description } }
4301
+ mcpServers { nodes { id, name, url } }
4302
+ }
4303
+ }
4304
+ }
4305
+ }
4306
+ }`;
4307
+ var LIST_BUILTIN_TOOLS_QUERY = `
4308
+ query AiCatalogBuiltInTools {
4309
+ aiCatalogBuiltInTools(first: 1000) {
4310
+ nodes {
4311
+ id
4312
+ name
4313
+ title
4314
+ description
4315
+ }
4316
+ }
4317
+ }`;
4198
4318
  function normalizeItemGid(id) {
4199
4319
  if (id.startsWith("gid://")) return id;
4200
4320
  if (!/^\d+$/.test(id)) throw new Error(`Invalid catalog item ID: "${id}"`);
@@ -4267,6 +4387,49 @@ async function disableAiCatalogItemForProject(instanceUrl, token, projectPath, i
4267
4387
  }
4268
4388
  return result.aiCatalogItemConsumerDelete;
4269
4389
  }
4390
+ async function createAgent(instanceUrl, token, projectPath, params) {
4391
+ const projectGid = await resolveProjectGid(instanceUrl, token, projectPath);
4392
+ const input = {
4393
+ projectId: projectGid,
4394
+ name: params.name,
4395
+ description: params.description,
4396
+ public: params.public,
4397
+ systemPrompt: params.systemPrompt
4398
+ };
4399
+ if (params.userPrompt) input.userPrompt = params.userPrompt;
4400
+ if (params.tools?.length) input.tools = params.tools;
4401
+ if (params.mcpTools?.length) input.mcpTools = params.mcpTools;
4402
+ if (params.mcpServers?.length) input.mcpServers = params.mcpServers;
4403
+ if (params.release !== void 0) input.release = params.release;
4404
+ const result = await gql(instanceUrl, token, CREATE_AGENT_MUTATION, { input });
4405
+ if (result.aiCatalogAgentCreate.errors.length > 0) {
4406
+ throw new Error(`Failed to create agent: ${result.aiCatalogAgentCreate.errors.join(", ")}`);
4407
+ }
4408
+ return result.aiCatalogAgentCreate.item;
4409
+ }
4410
+ async function updateAgent(instanceUrl, token, itemId, params) {
4411
+ const gid = normalizeItemGid(itemId);
4412
+ const input = { id: gid };
4413
+ if (params.name !== void 0) input.name = params.name;
4414
+ if (params.description !== void 0) input.description = params.description;
4415
+ if (params.public !== void 0) input.public = params.public;
4416
+ if (params.systemPrompt !== void 0) input.systemPrompt = params.systemPrompt;
4417
+ if (params.userPrompt !== void 0) input.userPrompt = params.userPrompt;
4418
+ if (params.tools !== void 0) input.tools = params.tools;
4419
+ if (params.mcpTools !== void 0) input.mcpTools = params.mcpTools;
4420
+ if (params.mcpServers !== void 0) input.mcpServers = params.mcpServers;
4421
+ if (params.release !== void 0) input.release = params.release;
4422
+ if (params.versionBump) input.versionBump = params.versionBump;
4423
+ const result = await gql(instanceUrl, token, UPDATE_AGENT_MUTATION, { input });
4424
+ if (result.aiCatalogAgentUpdate.errors.length > 0) {
4425
+ throw new Error(`Failed to update agent: ${result.aiCatalogAgentUpdate.errors.join(", ")}`);
4426
+ }
4427
+ return result.aiCatalogAgentUpdate.item;
4428
+ }
4429
+ async function listBuiltInTools(instanceUrl, token) {
4430
+ const result = await gql(instanceUrl, token, LIST_BUILTIN_TOOLS_QUERY, {});
4431
+ return result.aiCatalogBuiltInTools?.nodes ?? [];
4432
+ }
4270
4433
 
4271
4434
  // src/agents.ts
4272
4435
  function resolveModelId(entry) {
@@ -4429,13 +4592,73 @@ function readAuth() {
4429
4592
  return null;
4430
4593
  }
4431
4594
  }
4595
+ function buildFlowSubagentPrompt(flow, projectPath, projectUrl) {
4596
+ return [
4597
+ `You execute the "${flow.name}" GitLab flow. Project: ${projectPath} (${projectUrl}).`,
4598
+ ``,
4599
+ `STEP 1: Call gitlab_get_flow_definition with consumer_id=${flow.consumerId}, foundational=${!!flow.foundational}.`,
4600
+ `Parse the YAML config:`,
4601
+ `- In "components", find { from: "context:goal", as: "<name>" }. The "as" value is what the goal parameter must contain:`,
4602
+ ` "merge_request_iid" -> just the number (e.g. 14)`,
4603
+ ` "pipeline_url"/"url"/"issue_url" -> full URL (e.g. ${projectUrl}/-/merge_requests/5)`,
4604
+ ` "vulnerability_id" -> just the ID number`,
4605
+ ` "goal" -> free-form text`,
4606
+ `- In "flow.inputs", find additional_context categories (skip "agent_platform_standard_context").`,
4607
+ ``,
4608
+ `STEP 2: Resolve the goal to the EXACT value the flow expects (from step 1).`,
4609
+ `The goal parameter has a 10000 char limit and is used directly as the flow parameter \u2014 pass ONLY the raw value, never a sentence.`,
4610
+ `Use GitLab API tools if needed to look up IDs or construct URLs from the user's message.`,
4611
+ ``,
4612
+ `STEP 3: Gather additional_context values (if any from step 1) using available tools.`,
4613
+ ``,
4614
+ `STEP 4: Call gitlab_execute_project_flow with:`,
4615
+ ` project_id: "${projectPath}"`,
4616
+ ` consumer_id: ${flow.consumerId}`,
4617
+ ` goal: <resolved value from step 2>`,
4618
+ ` additional_context (if needed): [{"Category":"<cat>","Content":"{\\"field\\":\\"val\\"}"}]`,
4619
+ ``,
4620
+ `STEP 5: Call gitlab_get_workflow_status with the workflow_id. Report status and URL: ${projectUrl}/-/automate/agent-sessions/<id>`
4621
+ ].join("\n");
4622
+ }
4432
4623
  var memo = /* @__PURE__ */ new Map();
4624
+ var FLOW_DISPATCH_GUIDELINES = [
4625
+ `## GitLab Flow Dispatch Guidelines`,
4626
+ ``,
4627
+ `CRITICAL: You must NEVER call gitlab_execute_project_flow or gitlab_get_flow_definition directly.`,
4628
+ `Flows are ALWAYS executed via the Task tool with subagent_type "general".`,
4629
+ `When the user's message contains flow dispatch instructions (starting with "IMPORTANT: You MUST"),`,
4630
+ `follow those instructions exactly \u2014 call the Task tool with the provided parameters.`,
4631
+ ``,
4632
+ `### Multiple Flows or Resources`,
4633
+ `When multiple flows need to run (multiple @mentions, or batch across resources), dispatch them`,
4634
+ `via a SINGLE "general" subagent. The general subagent can execute multiple tool calls in parallel,`,
4635
+ `so all flows fire simultaneously. Do NOT dispatch multiple Task calls \u2014 use ONE Task with a prompt`,
4636
+ `that lists all the flows to execute, so the subagent runs them concurrently.`,
4637
+ ``,
4638
+ `### Batch Operations (Multiple Resources)`,
4639
+ `If the user asks to run flows on multiple resources (e.g., "for each MR"), first list the`,
4640
+ `resources yourself using GitLab API tools, then dispatch ONE general subagent whose prompt`,
4641
+ `includes all flow executions (N flows x M resources) to run in parallel.`
4642
+ ].join("\n");
4643
+ var AGENT_CREATION_GUIDELINES = `## Creating Custom GitLab Agents
4644
+
4645
+ Before calling gitlab_create_agent, you MUST:
4646
+ 1. Call gitlab_list_builtin_tools and gitlab_list_project_mcp_servers.
4647
+ 2. Ask the user 4 questions using the question tool (one call, all 4 questions):
4648
+ - Agent name (suggest one, allow custom)
4649
+ - Visibility: Public or Private
4650
+ - Tools: show tools grouped by category as multi-select (Search, Issues, MRs, Epics, Files, Git, CI/CD, Security, Audit, Planning, Wiki, API)
4651
+ - MCP servers: multi-select from available servers
4652
+ 3. Show the generated system prompt and ask for confirmation.
4653
+ 4. Only then call gitlab_create_agent. Use full tool GIDs like "gid://gitlab/Ai::Catalog::BuiltInTool/1".
4654
+ 5. Ask if the user wants to enable it on the current project.`;
4433
4655
  var plugin = async (input) => {
4434
4656
  let authCache = null;
4435
4657
  let projectPath;
4436
4658
  let namespaceId;
4437
4659
  const flowAgents = /* @__PURE__ */ new Map();
4438
4660
  const gitlabAgentNames = /* @__PURE__ */ new Set();
4661
+ let cachedAgents = [];
4439
4662
  let cfgRef = null;
4440
4663
  let baseModelIdRef;
4441
4664
  async function load2() {
@@ -4479,10 +4702,14 @@ var plugin = async (input) => {
4479
4702
  currentNames.add(dName);
4480
4703
  if (isFlow && agent.consumerId && projectPath) {
4481
4704
  flowAgents.set(dName, agent);
4705
+ const rBaseUrl = authCache?.instanceUrl?.replace(/\/$/, "") ?? "https://gitlab.com";
4706
+ const rProjectUrl = `${rBaseUrl}/${projectPath}`;
4482
4707
  cfgRef.agent[dName] = {
4483
4708
  name: dName,
4484
4709
  description: `[GitLab Flow] ${agent.description}`,
4485
- mode: "subagent"
4710
+ mode: "subagent",
4711
+ prompt: buildFlowSubagentPrompt(agent, projectPath, rProjectUrl),
4712
+ permission: { "*": "allow" }
4486
4713
  };
4487
4714
  } else {
4488
4715
  gitlabAgentNames.add(dName);
@@ -4511,20 +4738,25 @@ var plugin = async (input) => {
4511
4738
  async config(cfg) {
4512
4739
  const result = await load2();
4513
4740
  if (!result?.agents.length) return;
4741
+ cachedAgents = result.agents;
4514
4742
  const baseModelId = resolveModelId(result.entry);
4515
4743
  cfg.agent ??= {};
4516
4744
  cfgRef = cfg;
4517
4745
  baseModelIdRef = baseModelId;
4746
+ const cfgBaseUrl = authCache?.instanceUrl?.replace(/\/$/, "") ?? "https://gitlab.com";
4518
4747
  for (const agent of result.agents) {
4519
4748
  const isFlow = agent.itemType === "FLOW";
4520
4749
  const icon = agent.foundational ? " \u{1F98A}" : "";
4521
4750
  if (isFlow && agent.consumerId && projectPath) {
4522
4751
  const displayName = `${agent.name}${icon}`;
4523
4752
  flowAgents.set(displayName, agent);
4753
+ const pUrl = `${cfgBaseUrl}/${projectPath}`;
4524
4754
  cfg.agent[displayName] = {
4525
4755
  name: displayName,
4526
4756
  description: `[GitLab Flow] ${agent.description}`,
4527
- mode: "subagent"
4757
+ mode: "subagent",
4758
+ prompt: buildFlowSubagentPrompt(agent, projectPath, pUrl),
4759
+ permission: { "*": "allow" }
4528
4760
  };
4529
4761
  } else {
4530
4762
  const displayName = `${agent.name}${icon}`;
@@ -4547,83 +4779,105 @@ var plugin = async (input) => {
4547
4779
  },
4548
4780
  "chat.message": async (_input, output) => {
4549
4781
  const indicesToRemove = [];
4550
- const replacements = [];
4782
+ const flowMentions = [];
4551
4783
  for (let i = 0; i < output.parts.length; i++) {
4552
4784
  const part = output.parts[i];
4553
4785
  if (part.type !== "agent") continue;
4554
4786
  const flow = flowAgents.get(part.name);
4555
4787
  if (!flow || !flow.consumerId || !projectPath) continue;
4556
- const rawText = output.parts.filter((p) => p.type === "text" && !p.synthetic).map((p) => p.text).join(" ").trim() || "Execute the flow";
4557
- const baseUrl = authCache?.instanceUrl?.replace(/\/$/, "") ?? "https://gitlab.com";
4558
- const projectUrl = `${baseUrl}/${projectPath}`;
4559
- const subagentPrompt = [
4560
- `Execute the "${flow.name}" GitLab flow on project ${projectPath} (${projectUrl}).`,
4561
- `User goal: "${rawText}"`,
4562
- ``,
4563
- `You have access to ALL available tools: GitLab API tools (gitlab_list_merge_requests, gitlab_get_merge_request, gitlab_list_pipelines, gitlab_get_pipeline, gitlab_list_issues, gitlab_get_issue, gitlab_search, etc.), MCP servers, file tools, and any other tools in your environment. Use whatever tools you need to gather the required flow inputs.`,
4564
- ``,
4565
- `STEP 1 - FETCH AND DISPLAY FLOW DEFINITION:`,
4566
- `Call gitlab_get_flow_definition with consumer_id: ${flow.consumerId} and foundational: ${!!flow.foundational}.`,
4567
- `The response has a "config" field containing YAML. Parse it and find the "flow:" \u2192 "inputs:" section.`,
4568
- `Each input has a "category" and "input_schema" with field names and types.`,
4569
- `Print ALL inputs you found. Skip "agent_platform_standard_context" (auto-injected by server).`,
4570
- `If the config has no "inputs" or "inputs: []", say "No required inputs" and skip to step 3.`,
4571
- ``,
4572
- `STEP 2 - GATHER INPUT VALUES:`,
4573
- `For each required input from step 1, be creative and resourceful in finding the real values:`,
4574
- `- Use ANY available tools to find the data: GitLab tools, MCP servers, search, file reads, etc.`,
4575
- `- Look at the user's goal for hints (MR numbers, pipeline IDs, branch names, URLs)`,
4576
- `- For URL fields: construct full GitLab URLs using base ${projectUrl}`,
4577
- `- For branch fields: look up from MRs, pipelines, or project default branch`,
4578
- `- For IDs: extract from the user's message or look up via GitLab API`,
4579
- `- If an input cannot be determined, ask yourself what makes sense given the context`,
4580
- `Print each resolved input value before proceeding.`,
4581
- ``,
4582
- `STEP 3 - EXECUTE THE FLOW:`,
4583
- `Call gitlab_execute_project_flow with:`,
4584
- `- project_id: "${projectPath}"`,
4585
- `- consumer_id: ${flow.consumerId}`,
4586
- `- goal: the user's goal with any relevant URLs/context appended`,
4587
- `- additional_context: JSON array of inputs from step 2, each entry: {"Category":"<category>","Content":"{\\"field\\":\\"value\\"}"}`,
4588
- ` Example: [{"Category":"merge_request","Content":"{\\"url\\":\\"${projectUrl}/-/merge_requests/12\\"}"}]`,
4589
- ``,
4590
- `STEP 4 - CONFIRM FLOW STARTED:`,
4591
- `Call gitlab_get_workflow_status with the returned workflow_id.`,
4592
- `The tool blocks automatically while status is CREATED (up to 2 minutes).`,
4593
- `Once it returns, report:`,
4594
- `- The workflow status (RUNNING, FINISHED, FAILED, etc.)`,
4595
- `- The workflow URL: ${projectUrl}/-/automate/agent-sessions/<workflow_id>`,
4596
- `- A brief summary of what was executed`,
4597
- `Then stop. Do NOT keep polling. The user can follow the flow execution in the GitLab UI.`
4598
- ].join("\n");
4788
+ flowMentions.push({ idx: i, flow, displayName: part.name });
4789
+ if (i + 1 < output.parts.length) {
4790
+ const next = output.parts[i + 1];
4791
+ if (next.type === "text" && next.synthetic && next.text?.includes("call the task tool with subagent")) {
4792
+ indicesToRemove.push(i + 1);
4793
+ }
4794
+ }
4795
+ }
4796
+ if (flowMentions.length === 0) {
4797
+ return;
4798
+ }
4799
+ if (flowMentions.length === 1) {
4800
+ const { idx, flow } = flowMentions[0];
4801
+ const baseUrl2 = authCache?.instanceUrl?.replace(/\/$/, "") ?? "https://gitlab.com";
4802
+ const projectUrl2 = `${baseUrl2}/${projectPath}`;
4803
+ const rawText2 = output.parts.filter((p) => p.type === "text" && !p.synthetic).map((p) => p.text).join(" ").trim() || "Execute the flow";
4804
+ const subagentPrompt = buildFlowSubagentPrompt(flow, projectPath, projectUrl2) + `
4805
+
4806
+ User goal: "${rawText2}"`;
4599
4807
  const resultText = [
4600
4808
  `IMPORTANT: You MUST call the Task tool RIGHT NOW to dispatch a subagent. Do NOT execute these steps yourself.`,
4601
4809
  ``,
4602
4810
  `Call the Task tool with:`,
4811
+ ` subagent_type: "general"`,
4603
4812
  ` description: "Execute ${flow.name} flow"`,
4604
4813
  ` prompt: ${JSON.stringify(subagentPrompt)}`,
4605
4814
  ``,
4606
4815
  `Do not do anything else. Just call the Task tool with the above parameters.`
4607
4816
  ].join("\n");
4608
- replacements.push({ idx: i, text: resultText });
4609
- if (i + 1 < output.parts.length) {
4610
- const next = output.parts[i + 1];
4611
- if (next.type === "text" && next.synthetic && next.text?.includes("call the task tool with subagent")) {
4612
- indicesToRemove.push(i + 1);
4613
- }
4614
- }
4615
- }
4616
- for (const { idx, text } of replacements) {
4617
- const original = output.parts[idx];
4618
- output.parts[idx] = {
4619
- ...original,
4620
- type: "text",
4621
- text
4622
- };
4817
+ const original2 = output.parts[idx];
4818
+ output.parts[idx] = { ...original2, type: "text", text: resultText };
4623
4819
  delete output.parts[idx].name;
4624
4820
  delete output.parts[idx].source;
4821
+ for (const rmIdx of indicesToRemove.reverse()) {
4822
+ output.parts.splice(rmIdx, 1);
4823
+ }
4824
+ return;
4625
4825
  }
4626
- for (const idx of indicesToRemove.reverse()) {
4826
+ const baseUrl = authCache?.instanceUrl?.replace(/\/$/, "") ?? "https://gitlab.com";
4827
+ const projectUrl = `${baseUrl}/${projectPath}`;
4828
+ const flowNames = new Set(flowMentions.map((m) => m.displayName));
4829
+ let rawText = output.parts.filter((p) => p.type === "text" && !p.synthetic).map((p) => p.text).join(" ").trim() || "Execute the flows";
4830
+ for (const name of flowNames) {
4831
+ rawText = rawText.replace(new RegExp(`@${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "g"), "").trim();
4832
+ }
4833
+ rawText = rawText.replace(/\s{2,}/g, " ").trim() || "Execute the flows";
4834
+ const flowList = flowMentions.map(
4835
+ ({ flow, displayName }, i) => `${i + 1}. "${displayName}" \u2014 consumer_id=${flow.consumerId}, foundational=${!!flow.foundational}`
4836
+ );
4837
+ const batchPrompt = [
4838
+ `Execute ${flowMentions.length} GitLab flows on project ${projectPath} (${projectUrl}).`,
4839
+ `User goal: "${rawText}"`,
4840
+ ``,
4841
+ `Flows to execute:`,
4842
+ ...flowList,
4843
+ ``,
4844
+ `EXECUTION PLAN:`,
4845
+ `1. Call gitlab_get_flow_definition for ALL flows listed above in a SINGLE response (${flowMentions.length} tool calls at once).`,
4846
+ ` Parse each YAML to find what "context:goal" maps to (the "as" field in components).`,
4847
+ ``,
4848
+ `2. If the user's goal involves multiple resources (e.g., "for each MR"), list them using GitLab API tools.`,
4849
+ ``,
4850
+ `3. Call gitlab_execute_project_flow for EVERY flow+resource combination in a SINGLE response.`,
4851
+ ` For each call, set the goal to the EXACT value the flow expects (e.g., just "14" for merge_request_iid).`,
4852
+ ` project_id: "${projectPath}"`,
4853
+ ` You MUST emit ALL execute calls in ONE response \u2014 do NOT wait for one to finish before calling the next.`,
4854
+ ``,
4855
+ `4. Collect all workflow_ids from step 3. Call gitlab_get_workflow_status for ALL of them in a SINGLE response.`,
4856
+ ``,
4857
+ `5. Present a summary table: flow name, resource, status, URL (${projectUrl}/-/automate/agent-sessions/<id>).`,
4858
+ ``,
4859
+ `CRITICAL: In steps 1, 3, and 4 you MUST make multiple tool calls in the SAME response for parallel execution.`
4860
+ ].join("\n");
4861
+ const combinedText = [
4862
+ `IMPORTANT: You MUST call the Task tool RIGHT NOW with subagent_type "general" to dispatch all flows in parallel.`,
4863
+ `Do NOT call flow tools yourself. Do NOT dispatch multiple Task calls \u2014 use ONE.`,
4864
+ ``,
4865
+ `Call the Task tool with:`,
4866
+ ` subagent_type: "general"`,
4867
+ ` description: "Execute ${flowMentions.length} flows in parallel"`,
4868
+ ` prompt: ${JSON.stringify(batchPrompt)}`,
4869
+ ``,
4870
+ `Do not do anything else. Just call the Task tool with the above parameters.`
4871
+ ].join("\n");
4872
+ const firstIdx = flowMentions[0].idx;
4873
+ const original = output.parts[firstIdx];
4874
+ output.parts[firstIdx] = { ...original, type: "text", text: combinedText };
4875
+ delete output.parts[firstIdx].name;
4876
+ delete output.parts[firstIdx].source;
4877
+ for (let i = flowMentions.length - 1; i >= 1; i--) {
4878
+ indicesToRemove.push(flowMentions[i].idx);
4879
+ }
4880
+ for (const idx of [...new Set(indicesToRemove)].sort((a, b) => b - a)) {
4627
4881
  output.parts.splice(idx, 1);
4628
4882
  }
4629
4883
  },
@@ -4639,6 +4893,14 @@ var plugin = async (input) => {
4639
4893
  );
4640
4894
  }
4641
4895
  },
4896
+ "experimental.chat.system.transform": async (_input, output) => {
4897
+ if (flowAgents.size) {
4898
+ output.system.push(FLOW_DISPATCH_GUIDELINES);
4899
+ }
4900
+ if (authCache) {
4901
+ output.system.push(AGENT_CREATION_GUIDELINES);
4902
+ }
4903
+ },
4642
4904
  tool: {
4643
4905
  gitlab_execute_project_flow: tool({
4644
4906
  description: "Execute a GitLab DAP flow on a project.\nTriggers a flow via the Duo Workflow Service REST API.\nThe flow runs asynchronously and is visible in the GitLab UI.\nReturns the workflow record with ID and status.\nThe additional_context parameter accepts flow-specific inputs as a JSON array of {Category, Content} objects.",
@@ -4737,6 +4999,134 @@ var plugin = async (input) => {
4737
4999
  }
4738
5000
  }
4739
5001
  }),
5002
+ gitlab_list_project_mcp_servers: tool({
5003
+ description: "List MCP servers available through agents enabled for a project.\nReturns deduplicated servers with name, URL, auth type, connection status, and which agents use them.",
5004
+ args: {
5005
+ project_id: z.string().describe('Project path (e.g., "gitlab-org/gitlab")')
5006
+ },
5007
+ execute: async (_args) => {
5008
+ const serverMap = /* @__PURE__ */ new Map();
5009
+ for (const agent of cachedAgents) {
5010
+ if (!agent.mcpServers?.length) continue;
5011
+ for (const server of agent.mcpServers) {
5012
+ const existing = serverMap.get(server.id);
5013
+ if (existing) {
5014
+ if (!existing.usedBy.includes(agent.name)) existing.usedBy.push(agent.name);
5015
+ } else {
5016
+ serverMap.set(server.id, { ...server, usedBy: [agent.name] });
5017
+ }
5018
+ }
5019
+ }
5020
+ const servers = [...serverMap.values()];
5021
+ if (!servers.length) {
5022
+ return "No MCP servers found for agents enabled in this project.";
5023
+ }
5024
+ return JSON.stringify(servers, null, 2);
5025
+ }
5026
+ }),
5027
+ gitlab_create_agent: tool({
5028
+ description: "Create a new custom agent in the GitLab AI Catalog.\nFirst call: set confirmed=false (or omit). The tool returns without creating anything and instructs you to ask the user for agent properties using the question tool.\nSecond call: after the user confirms, set confirmed=true to actually create the agent.\nAfter creation, use gitlab_enable_project_agent to enable it on a project.",
5029
+ args: {
5030
+ project_id: z.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
5031
+ name: z.string().describe("Display name for the agent"),
5032
+ description: z.string().describe("Description of what the agent does"),
5033
+ public: z.boolean().describe("Whether the agent is publicly visible in the AI Catalog"),
5034
+ system_prompt: z.string().describe("System prompt that defines the agent's behavior"),
5035
+ user_prompt: z.string().optional().describe("User prompt template (optional)"),
5036
+ tools: z.array(z.string()).optional().describe(
5037
+ 'Array of built-in tool Global IDs from gitlab_list_builtin_tools. Must be full GIDs like "gid://gitlab/Ai::Catalog::BuiltInTool/1", NOT tool names.'
5038
+ ),
5039
+ mcp_tools: z.array(z.string()).optional().describe("Array of MCP tool names to enable"),
5040
+ mcp_servers: z.array(z.string()).optional().describe(
5041
+ 'Array of MCP server Global IDs from gitlab_list_project_mcp_servers. Must be full GIDs like "gid://gitlab/Ai::Catalog::McpServer/1", NOT server names.'
5042
+ ),
5043
+ release: z.boolean().optional().describe("Whether to release the version immediately (default: false)"),
5044
+ confirmed: z.boolean().optional().describe(
5045
+ "Set to true only after the user has reviewed and confirmed all parameters. Omit or set false on first call."
5046
+ )
5047
+ },
5048
+ execute: async (args) => {
5049
+ if (!args.confirmed) {
5050
+ return [
5051
+ "STOP: Do not create the agent yet. You must ask the user to confirm the configuration first.",
5052
+ "",
5053
+ "Follow these steps NOW:",
5054
+ "1. Call gitlab_list_builtin_tools and gitlab_list_project_mcp_servers to discover options.",
5055
+ "2. Use the question tool to ask the user ALL 4 of these (in one call):",
5056
+ " - Agent name (suggest one, allow custom input)",
5057
+ " - Visibility: Public or Private",
5058
+ " - Tools: group by category (Search, Issues, MRs, Epics, Files, Git, CI/CD, Security, Audit, Planning, Wiki, API) as multi-select",
5059
+ " - MCP servers: multi-select from available servers",
5060
+ "3. Generate a system prompt and show it to the user for approval.",
5061
+ "4. Call gitlab_create_agent again with confirmed=true after the user approves."
5062
+ ].join("\n");
5063
+ }
5064
+ const auth = authCache ?? readAuth();
5065
+ if (!auth) throw new Error("Not authenticated");
5066
+ const result = await createAgent(auth.instanceUrl, auth.token, args.project_id, {
5067
+ name: args.name,
5068
+ description: args.description,
5069
+ public: args.public,
5070
+ systemPrompt: args.system_prompt,
5071
+ userPrompt: args.user_prompt,
5072
+ tools: args.tools,
5073
+ mcpTools: args.mcp_tools,
5074
+ mcpServers: args.mcp_servers,
5075
+ release: args.release
5076
+ });
5077
+ await refreshAgents();
5078
+ return JSON.stringify(result, null, 2);
5079
+ }
5080
+ }),
5081
+ gitlab_update_agent: tool({
5082
+ description: "Update an existing custom agent in the GitLab AI Catalog.\nOnly provided fields are updated; omitted fields remain unchanged.",
5083
+ args: {
5084
+ id: z.string().describe("Agent ID (numeric or full GID)"),
5085
+ name: z.string().optional().describe("New display name"),
5086
+ description: z.string().optional().describe("New description"),
5087
+ public: z.boolean().optional().describe("Whether publicly visible"),
5088
+ system_prompt: z.string().optional().describe("New system prompt"),
5089
+ user_prompt: z.string().optional().describe("New user prompt template"),
5090
+ tools: z.array(z.string()).optional().describe(
5091
+ 'New set of built-in tool Global IDs (full GIDs like "gid://gitlab/Ai::Catalog::BuiltInTool/1")'
5092
+ ),
5093
+ mcp_tools: z.array(z.string()).optional().describe("New set of MCP tool names"),
5094
+ mcp_servers: z.array(z.string()).optional().describe(
5095
+ 'New set of MCP server Global IDs (full GIDs like "gid://gitlab/Ai::Catalog::McpServer/1")'
5096
+ ),
5097
+ release: z.boolean().optional().describe("Whether to release the latest version"),
5098
+ version_bump: z.enum(["MAJOR", "MINOR", "PATCH"]).optional().describe("Version bump type")
5099
+ },
5100
+ execute: async (args) => {
5101
+ const auth = authCache ?? readAuth();
5102
+ if (!auth) throw new Error("Not authenticated");
5103
+ const result = await updateAgent(auth.instanceUrl, auth.token, args.id, {
5104
+ name: args.name,
5105
+ description: args.description,
5106
+ public: args.public,
5107
+ systemPrompt: args.system_prompt,
5108
+ userPrompt: args.user_prompt,
5109
+ tools: args.tools,
5110
+ mcpTools: args.mcp_tools,
5111
+ mcpServers: args.mcp_servers,
5112
+ release: args.release,
5113
+ versionBump: args.version_bump
5114
+ });
5115
+ await refreshAgents();
5116
+ return JSON.stringify(result, null, 2);
5117
+ }
5118
+ }),
5119
+ gitlab_list_builtin_tools: tool({
5120
+ description: "List available built-in GitLab tools that can be assigned to custom agents.\nReturns tool IDs, names, and descriptions. Use the IDs when creating or updating agents.",
5121
+ args: {},
5122
+ execute: async () => {
5123
+ const auth = authCache ?? readAuth();
5124
+ if (!auth) throw new Error("Not authenticated");
5125
+ const tools = await listBuiltInTools(auth.instanceUrl, auth.token);
5126
+ if (!tools.length) return "No built-in tools available.";
5127
+ return JSON.stringify(tools, null, 2);
5128
+ }
5129
+ }),
4740
5130
  ...makeAgentFlowTools(
4741
5131
  z,
4742
5132
  () => authCache,