opencode-gitlab-dap 1.4.3 → 1.5.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.
package/dist/index.cjs CHANGED
@@ -3157,6 +3157,7 @@ components:
3157
3157
  prompt_version: "^1.0.0"
3158
3158
  response_schema_id: "fix_pipeline_decide_approach"
3159
3159
  response_schema_version: "^1.0.0"
3160
+ response_schema_tracking: true
3160
3161
  inputs:
3161
3162
  - from: "context:fix_pipeline_context.final_answer"
3162
3163
  as: "fix_pipeline_context_results"
@@ -3501,6 +3502,7 @@ components:
3501
3502
  prompt_version: "^1.0.0"
3502
3503
  response_schema_id: "fix_pipeline_next_decide_approach"
3503
3504
  response_schema_version: "^1.0.0"
3505
+ response_schema_tracking: true
3504
3506
  inputs:
3505
3507
  - from: "context:fix_pipeline_context.final_answer"
3506
3508
  as: "fix_pipeline_context_results"
@@ -3945,6 +3947,61 @@ async function fetchCustomAgents(instanceUrl, token, projectId) {
3945
3947
  }
3946
3948
  return agents;
3947
3949
  }
3950
+ var MCP_SERVERS_QUERY = `
3951
+ query AiCatalogMcpServers($projectId: ProjectID!, $after: String) {
3952
+ aiCatalogConfiguredItems(first: 50, projectId: $projectId, itemTypes: [AGENT], after: $after) {
3953
+ pageInfo { hasNextPage endCursor }
3954
+ nodes {
3955
+ item {
3956
+ id
3957
+ name
3958
+ latestVersion {
3959
+ ... on AiCatalogAgentVersion {
3960
+ mcpServers {
3961
+ nodes { id name description url transport authType currentUserConnected }
3962
+ }
3963
+ }
3964
+ }
3965
+ }
3966
+ }
3967
+ }
3968
+ }`;
3969
+ async function fetchMcpServers(instanceUrl, token, projectId, agents) {
3970
+ try {
3971
+ let after = null;
3972
+ const serversByAgent = /* @__PURE__ */ new Map();
3973
+ for (; ; ) {
3974
+ const data = await gql(instanceUrl, token, MCP_SERVERS_QUERY, {
3975
+ projectId,
3976
+ ...after ? { after } : {}
3977
+ });
3978
+ const page = data?.aiCatalogConfiguredItems;
3979
+ if (!page) break;
3980
+ for (const node of page.nodes ?? []) {
3981
+ const item = node.item;
3982
+ const version = item?.latestVersion;
3983
+ if (!version?.mcpServers?.nodes?.length) continue;
3984
+ const servers = version.mcpServers.nodes.map((s) => ({
3985
+ id: s.id,
3986
+ name: s.name,
3987
+ description: s.description ?? "",
3988
+ url: s.url,
3989
+ transport: s.transport,
3990
+ authType: s.authType,
3991
+ currentUserConnected: !!s.currentUserConnected
3992
+ }));
3993
+ serversByAgent.set(item.id, servers);
3994
+ }
3995
+ if (!page.pageInfo?.hasNextPage) break;
3996
+ after = page.pageInfo.endCursor;
3997
+ }
3998
+ for (const agent of agents) {
3999
+ const servers = serversByAgent.get(agent.identifier);
4000
+ if (servers?.length) agent.mcpServers = servers;
4001
+ }
4002
+ } catch {
4003
+ }
4004
+ }
3948
4005
  async function getFlowDefinition(instanceUrl, token, opts) {
3949
4006
  const name = opts.flowName ?? `consumer-${opts.consumerId}`;
3950
4007
  let config = null;
@@ -4090,7 +4147,9 @@ async function fetchCatalogAgents(instanceUrl, token, projectId) {
4090
4147
  fetchFoundationalChatAgents(instanceUrl, token, projectId),
4091
4148
  fetchCustomAgents(instanceUrl, token, projectId)
4092
4149
  ]);
4093
- return [...foundational, ...custom];
4150
+ const agents = [...foundational, ...custom];
4151
+ await fetchMcpServers(instanceUrl, token, projectId, agents);
4152
+ return agents;
4094
4153
  } catch {
4095
4154
  return [];
4096
4155
  }
@@ -4229,6 +4288,67 @@ query resolveProjectIds($projectPath: ID!) {
4229
4288
  namespace { id }
4230
4289
  }
4231
4290
  }`;
4291
+ var CREATE_AGENT_MUTATION = `
4292
+ mutation AiCatalogAgentCreate($input: AiCatalogAgentCreateInput!) {
4293
+ aiCatalogAgentCreate(input: $input) {
4294
+ errors
4295
+ item {
4296
+ id
4297
+ name
4298
+ description
4299
+ itemType
4300
+ public
4301
+ project { id, nameWithNamespace, webUrl }
4302
+ latestVersion {
4303
+ id
4304
+ humanVersionName
4305
+ released
4306
+ ... on AiCatalogAgentVersion {
4307
+ systemPrompt
4308
+ userPrompt
4309
+ tools { nodes { id, name, description } }
4310
+ mcpServers { nodes { id, name, url } }
4311
+ }
4312
+ }
4313
+ }
4314
+ }
4315
+ }`;
4316
+ var UPDATE_AGENT_MUTATION = `
4317
+ mutation AiCatalogAgentUpdate($input: AiCatalogAgentUpdateInput!) {
4318
+ aiCatalogAgentUpdate(input: $input) {
4319
+ errors
4320
+ item {
4321
+ id
4322
+ name
4323
+ description
4324
+ itemType
4325
+ public
4326
+ project { id, nameWithNamespace, webUrl }
4327
+ latestVersion {
4328
+ id
4329
+ humanVersionName
4330
+ released
4331
+ ... on AiCatalogAgentVersion {
4332
+ systemPrompt
4333
+ userPrompt
4334
+ tools { nodes { id, name, description } }
4335
+ mcpServers { nodes { id, name, url } }
4336
+ }
4337
+ }
4338
+ }
4339
+ }
4340
+ }`;
4341
+ var LIST_BUILTIN_TOOLS_QUERY = `
4342
+ query AiCatalogBuiltInTools {
4343
+ aiCatalogBuiltInTools(first: 1000) {
4344
+ nodes {
4345
+ id
4346
+ name
4347
+ title
4348
+ description
4349
+ }
4350
+ }
4351
+ }`;
4232
4352
  function normalizeItemGid(id) {
4233
4353
  if (id.startsWith("gid://")) return id;
4234
4354
  if (!/^\d+$/.test(id)) throw new Error(`Invalid catalog item ID: "${id}"`);
@@ -4301,6 +4421,49 @@ async function disableAiCatalogItemForProject(instanceUrl, token, projectPath, i
4301
4421
  }
4302
4422
  return result.aiCatalogItemConsumerDelete;
4303
4423
  }
4424
+ async function createAgent(instanceUrl, token, projectPath, params) {
4425
+ const projectGid = await resolveProjectGid(instanceUrl, token, projectPath);
4426
+ const input = {
4427
+ projectId: projectGid,
4428
+ name: params.name,
4429
+ description: params.description,
4430
+ public: params.public,
4431
+ systemPrompt: params.systemPrompt
4432
+ };
4433
+ if (params.userPrompt) input.userPrompt = params.userPrompt;
4434
+ if (params.tools?.length) input.tools = params.tools;
4435
+ if (params.mcpTools?.length) input.mcpTools = params.mcpTools;
4436
+ if (params.mcpServers?.length) input.mcpServers = params.mcpServers;
4437
+ if (params.release !== void 0) input.release = params.release;
4438
+ const result = await gql(instanceUrl, token, CREATE_AGENT_MUTATION, { input });
4439
+ if (result.aiCatalogAgentCreate.errors.length > 0) {
4440
+ throw new Error(`Failed to create agent: ${result.aiCatalogAgentCreate.errors.join(", ")}`);
4441
+ }
4442
+ return result.aiCatalogAgentCreate.item;
4443
+ }
4444
+ async function updateAgent(instanceUrl, token, itemId, params) {
4445
+ const gid = normalizeItemGid(itemId);
4446
+ const input = { id: gid };
4447
+ if (params.name !== void 0) input.name = params.name;
4448
+ if (params.description !== void 0) input.description = params.description;
4449
+ if (params.public !== void 0) input.public = params.public;
4450
+ if (params.systemPrompt !== void 0) input.systemPrompt = params.systemPrompt;
4451
+ if (params.userPrompt !== void 0) input.userPrompt = params.userPrompt;
4452
+ if (params.tools !== void 0) input.tools = params.tools;
4453
+ if (params.mcpTools !== void 0) input.mcpTools = params.mcpTools;
4454
+ if (params.mcpServers !== void 0) input.mcpServers = params.mcpServers;
4455
+ if (params.release !== void 0) input.release = params.release;
4456
+ if (params.versionBump) input.versionBump = params.versionBump;
4457
+ const result = await gql(instanceUrl, token, UPDATE_AGENT_MUTATION, { input });
4458
+ if (result.aiCatalogAgentUpdate.errors.length > 0) {
4459
+ throw new Error(`Failed to update agent: ${result.aiCatalogAgentUpdate.errors.join(", ")}`);
4460
+ }
4461
+ return result.aiCatalogAgentUpdate.item;
4462
+ }
4463
+ async function listBuiltInTools(instanceUrl, token) {
4464
+ const result = await gql(instanceUrl, token, LIST_BUILTIN_TOOLS_QUERY, {});
4465
+ return result.aiCatalogBuiltInTools?.nodes ?? [];
4466
+ }
4304
4467
 
4305
4468
  // src/agents.ts
4306
4469
  function resolveModelId(entry) {
@@ -4463,13 +4626,73 @@ function readAuth() {
4463
4626
  return null;
4464
4627
  }
4465
4628
  }
4629
+ function buildFlowSubagentPrompt(flow, projectPath, projectUrl) {
4630
+ return [
4631
+ `You execute the "${flow.name}" GitLab flow. Project: ${projectPath} (${projectUrl}).`,
4632
+ ``,
4633
+ `STEP 1: Call gitlab_get_flow_definition with consumer_id=${flow.consumerId}, foundational=${!!flow.foundational}.`,
4634
+ `Parse the YAML config:`,
4635
+ `- In "components", find { from: "context:goal", as: "<name>" }. The "as" value is what the goal parameter must contain:`,
4636
+ ` "merge_request_iid" -> just the number (e.g. 14)`,
4637
+ ` "pipeline_url"/"url"/"issue_url" -> full URL (e.g. ${projectUrl}/-/merge_requests/5)`,
4638
+ ` "vulnerability_id" -> just the ID number`,
4639
+ ` "goal" -> free-form text`,
4640
+ `- In "flow.inputs", find additional_context categories (skip "agent_platform_standard_context").`,
4641
+ ``,
4642
+ `STEP 2: Resolve the goal to the EXACT value the flow expects (from step 1).`,
4643
+ `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.`,
4644
+ `Use GitLab API tools if needed to look up IDs or construct URLs from the user's message.`,
4645
+ ``,
4646
+ `STEP 3: Gather additional_context values (if any from step 1) using available tools.`,
4647
+ ``,
4648
+ `STEP 4: Call gitlab_execute_project_flow with:`,
4649
+ ` project_id: "${projectPath}"`,
4650
+ ` consumer_id: ${flow.consumerId}`,
4651
+ ` goal: <resolved value from step 2>`,
4652
+ ` additional_context (if needed): [{"Category":"<cat>","Content":"{\\"field\\":\\"val\\"}"}]`,
4653
+ ``,
4654
+ `STEP 5: Call gitlab_get_workflow_status with the workflow_id. Report status and URL: ${projectUrl}/-/automate/agent-sessions/<id>`
4655
+ ].join("\n");
4656
+ }
4466
4657
  var memo = /* @__PURE__ */ new Map();
4658
+ var FLOW_DISPATCH_GUIDELINES = [
4659
+ `## GitLab Flow Dispatch Guidelines`,
4660
+ ``,
4661
+ `CRITICAL: You must NEVER call gitlab_execute_project_flow or gitlab_get_flow_definition directly.`,
4662
+ `Flows are ALWAYS executed via the Task tool with subagent_type "general".`,
4663
+ `When the user's message contains flow dispatch instructions (starting with "IMPORTANT: You MUST"),`,
4664
+ `follow those instructions exactly \u2014 call the Task tool with the provided parameters.`,
4665
+ ``,
4666
+ `### Multiple Flows or Resources`,
4667
+ `When multiple flows need to run (multiple @mentions, or batch across resources), dispatch them`,
4668
+ `via a SINGLE "general" subagent. The general subagent can execute multiple tool calls in parallel,`,
4669
+ `so all flows fire simultaneously. Do NOT dispatch multiple Task calls \u2014 use ONE Task with a prompt`,
4670
+ `that lists all the flows to execute, so the subagent runs them concurrently.`,
4671
+ ``,
4672
+ `### Batch Operations (Multiple Resources)`,
4673
+ `If the user asks to run flows on multiple resources (e.g., "for each MR"), first list the`,
4674
+ `resources yourself using GitLab API tools, then dispatch ONE general subagent whose prompt`,
4675
+ `includes all flow executions (N flows x M resources) to run in parallel.`
4676
+ ].join("\n");
4677
+ var AGENT_CREATION_GUIDELINES = `## Creating Custom GitLab Agents
4678
+
4679
+ Before calling gitlab_create_agent, you MUST:
4680
+ 1. Call gitlab_list_builtin_tools and gitlab_list_project_mcp_servers.
4681
+ 2. Ask the user 4 questions using the question tool (one call, all 4 questions):
4682
+ - Agent name (suggest one, allow custom)
4683
+ - Visibility: Public or Private
4684
+ - Tools: show tools grouped by category as multi-select (Search, Issues, MRs, Epics, Files, Git, CI/CD, Security, Audit, Planning, Wiki, API)
4685
+ - MCP servers: multi-select from available servers
4686
+ 3. Show the generated system prompt and ask for confirmation.
4687
+ 4. Only then call gitlab_create_agent. Use full tool GIDs like "gid://gitlab/Ai::Catalog::BuiltInTool/1".
4688
+ 5. Ask if the user wants to enable it on the current project.`;
4467
4689
  var plugin = async (input) => {
4468
4690
  let authCache = null;
4469
4691
  let projectPath;
4470
4692
  let namespaceId;
4471
4693
  const flowAgents = /* @__PURE__ */ new Map();
4472
4694
  const gitlabAgentNames = /* @__PURE__ */ new Set();
4695
+ let cachedAgents = [];
4473
4696
  let cfgRef = null;
4474
4697
  let baseModelIdRef;
4475
4698
  async function load2() {
@@ -4513,10 +4736,14 @@ var plugin = async (input) => {
4513
4736
  currentNames.add(dName);
4514
4737
  if (isFlow && agent.consumerId && projectPath) {
4515
4738
  flowAgents.set(dName, agent);
4739
+ const rBaseUrl = authCache?.instanceUrl?.replace(/\/$/, "") ?? "https://gitlab.com";
4740
+ const rProjectUrl = `${rBaseUrl}/${projectPath}`;
4516
4741
  cfgRef.agent[dName] = {
4517
4742
  name: dName,
4518
4743
  description: `[GitLab Flow] ${agent.description}`,
4519
- mode: "subagent"
4744
+ mode: "subagent",
4745
+ prompt: buildFlowSubagentPrompt(agent, projectPath, rProjectUrl),
4746
+ permission: { "*": "allow" }
4520
4747
  };
4521
4748
  } else {
4522
4749
  gitlabAgentNames.add(dName);
@@ -4545,20 +4772,25 @@ var plugin = async (input) => {
4545
4772
  async config(cfg) {
4546
4773
  const result = await load2();
4547
4774
  if (!result?.agents.length) return;
4775
+ cachedAgents = result.agents;
4548
4776
  const baseModelId = resolveModelId(result.entry);
4549
4777
  cfg.agent ??= {};
4550
4778
  cfgRef = cfg;
4551
4779
  baseModelIdRef = baseModelId;
4780
+ const cfgBaseUrl = authCache?.instanceUrl?.replace(/\/$/, "") ?? "https://gitlab.com";
4552
4781
  for (const agent of result.agents) {
4553
4782
  const isFlow = agent.itemType === "FLOW";
4554
4783
  const icon = agent.foundational ? " \u{1F98A}" : "";
4555
4784
  if (isFlow && agent.consumerId && projectPath) {
4556
4785
  const displayName = `${agent.name}${icon}`;
4557
4786
  flowAgents.set(displayName, agent);
4787
+ const pUrl = `${cfgBaseUrl}/${projectPath}`;
4558
4788
  cfg.agent[displayName] = {
4559
4789
  name: displayName,
4560
4790
  description: `[GitLab Flow] ${agent.description}`,
4561
- mode: "subagent"
4791
+ mode: "subagent",
4792
+ prompt: buildFlowSubagentPrompt(agent, projectPath, pUrl),
4793
+ permission: { "*": "allow" }
4562
4794
  };
4563
4795
  } else {
4564
4796
  const displayName = `${agent.name}${icon}`;
@@ -4581,83 +4813,105 @@ var plugin = async (input) => {
4581
4813
  },
4582
4814
  "chat.message": async (_input, output) => {
4583
4815
  const indicesToRemove = [];
4584
- const replacements = [];
4816
+ const flowMentions = [];
4585
4817
  for (let i = 0; i < output.parts.length; i++) {
4586
4818
  const part = output.parts[i];
4587
4819
  if (part.type !== "agent") continue;
4588
4820
  const flow = flowAgents.get(part.name);
4589
4821
  if (!flow || !flow.consumerId || !projectPath) continue;
4590
- const rawText = output.parts.filter((p) => p.type === "text" && !p.synthetic).map((p) => p.text).join(" ").trim() || "Execute the flow";
4591
- const baseUrl = authCache?.instanceUrl?.replace(/\/$/, "") ?? "https://gitlab.com";
4592
- const projectUrl = `${baseUrl}/${projectPath}`;
4593
- const subagentPrompt = [
4594
- `Execute the "${flow.name}" GitLab flow on project ${projectPath} (${projectUrl}).`,
4595
- `User goal: "${rawText}"`,
4596
- ``,
4597
- `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.`,
4598
- ``,
4599
- `STEP 1 - FETCH AND DISPLAY FLOW DEFINITION:`,
4600
- `Call gitlab_get_flow_definition with consumer_id: ${flow.consumerId} and foundational: ${!!flow.foundational}.`,
4601
- `The response has a "config" field containing YAML. Parse it and find the "flow:" \u2192 "inputs:" section.`,
4602
- `Each input has a "category" and "input_schema" with field names and types.`,
4603
- `Print ALL inputs you found. Skip "agent_platform_standard_context" (auto-injected by server).`,
4604
- `If the config has no "inputs" or "inputs: []", say "No required inputs" and skip to step 3.`,
4605
- ``,
4606
- `STEP 2 - GATHER INPUT VALUES:`,
4607
- `For each required input from step 1, be creative and resourceful in finding the real values:`,
4608
- `- Use ANY available tools to find the data: GitLab tools, MCP servers, search, file reads, etc.`,
4609
- `- Look at the user's goal for hints (MR numbers, pipeline IDs, branch names, URLs)`,
4610
- `- For URL fields: construct full GitLab URLs using base ${projectUrl}`,
4611
- `- For branch fields: look up from MRs, pipelines, or project default branch`,
4612
- `- For IDs: extract from the user's message or look up via GitLab API`,
4613
- `- If an input cannot be determined, ask yourself what makes sense given the context`,
4614
- `Print each resolved input value before proceeding.`,
4615
- ``,
4616
- `STEP 3 - EXECUTE THE FLOW:`,
4617
- `Call gitlab_execute_project_flow with:`,
4618
- `- project_id: "${projectPath}"`,
4619
- `- consumer_id: ${flow.consumerId}`,
4620
- `- goal: the user's goal with any relevant URLs/context appended`,
4621
- `- additional_context: JSON array of inputs from step 2, each entry: {"Category":"<category>","Content":"{\\"field\\":\\"value\\"}"}`,
4622
- ` Example: [{"Category":"merge_request","Content":"{\\"url\\":\\"${projectUrl}/-/merge_requests/12\\"}"}]`,
4623
- ``,
4624
- `STEP 4 - CONFIRM FLOW STARTED:`,
4625
- `Call gitlab_get_workflow_status with the returned workflow_id.`,
4626
- `The tool blocks automatically while status is CREATED (up to 2 minutes).`,
4627
- `Once it returns, report:`,
4628
- `- The workflow status (RUNNING, FINISHED, FAILED, etc.)`,
4629
- `- The workflow URL: ${projectUrl}/-/automate/agent-sessions/<workflow_id>`,
4630
- `- A brief summary of what was executed`,
4631
- `Then stop. Do NOT keep polling. The user can follow the flow execution in the GitLab UI.`
4632
- ].join("\n");
4822
+ flowMentions.push({ idx: i, flow, displayName: part.name });
4823
+ if (i + 1 < output.parts.length) {
4824
+ const next = output.parts[i + 1];
4825
+ if (next.type === "text" && next.synthetic && next.text?.includes("call the task tool with subagent")) {
4826
+ indicesToRemove.push(i + 1);
4827
+ }
4828
+ }
4829
+ }
4830
+ if (flowMentions.length === 0) {
4831
+ return;
4832
+ }
4833
+ if (flowMentions.length === 1) {
4834
+ const { idx, flow } = flowMentions[0];
4835
+ const baseUrl2 = authCache?.instanceUrl?.replace(/\/$/, "") ?? "https://gitlab.com";
4836
+ const projectUrl2 = `${baseUrl2}/${projectPath}`;
4837
+ const rawText2 = output.parts.filter((p) => p.type === "text" && !p.synthetic).map((p) => p.text).join(" ").trim() || "Execute the flow";
4838
+ const subagentPrompt = buildFlowSubagentPrompt(flow, projectPath, projectUrl2) + `
4839
+
4840
+ User goal: "${rawText2}"`;
4633
4841
  const resultText = [
4634
4842
  `IMPORTANT: You MUST call the Task tool RIGHT NOW to dispatch a subagent. Do NOT execute these steps yourself.`,
4635
4843
  ``,
4636
4844
  `Call the Task tool with:`,
4845
+ ` subagent_type: "general"`,
4637
4846
  ` description: "Execute ${flow.name} flow"`,
4638
4847
  ` prompt: ${JSON.stringify(subagentPrompt)}`,
4639
4848
  ``,
4640
4849
  `Do not do anything else. Just call the Task tool with the above parameters.`
4641
4850
  ].join("\n");
4642
- replacements.push({ idx: i, text: resultText });
4643
- if (i + 1 < output.parts.length) {
4644
- const next = output.parts[i + 1];
4645
- if (next.type === "text" && next.synthetic && next.text?.includes("call the task tool with subagent")) {
4646
- indicesToRemove.push(i + 1);
4647
- }
4648
- }
4649
- }
4650
- for (const { idx, text } of replacements) {
4651
- const original = output.parts[idx];
4652
- output.parts[idx] = {
4653
- ...original,
4654
- type: "text",
4655
- text
4656
- };
4851
+ const original2 = output.parts[idx];
4852
+ output.parts[idx] = { ...original2, type: "text", text: resultText };
4657
4853
  delete output.parts[idx].name;
4658
4854
  delete output.parts[idx].source;
4855
+ for (const rmIdx of indicesToRemove.reverse()) {
4856
+ output.parts.splice(rmIdx, 1);
4857
+ }
4858
+ return;
4659
4859
  }
4660
- for (const idx of indicesToRemove.reverse()) {
4860
+ const baseUrl = authCache?.instanceUrl?.replace(/\/$/, "") ?? "https://gitlab.com";
4861
+ const projectUrl = `${baseUrl}/${projectPath}`;
4862
+ const flowNames = new Set(flowMentions.map((m) => m.displayName));
4863
+ let rawText = output.parts.filter((p) => p.type === "text" && !p.synthetic).map((p) => p.text).join(" ").trim() || "Execute the flows";
4864
+ for (const name of flowNames) {
4865
+ rawText = rawText.replace(new RegExp(`@${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "g"), "").trim();
4866
+ }
4867
+ rawText = rawText.replace(/\s{2,}/g, " ").trim() || "Execute the flows";
4868
+ const flowList = flowMentions.map(
4869
+ ({ flow, displayName }, i) => `${i + 1}. "${displayName}" \u2014 consumer_id=${flow.consumerId}, foundational=${!!flow.foundational}`
4870
+ );
4871
+ const batchPrompt = [
4872
+ `Execute ${flowMentions.length} GitLab flows on project ${projectPath} (${projectUrl}).`,
4873
+ `User goal: "${rawText}"`,
4874
+ ``,
4875
+ `Flows to execute:`,
4876
+ ...flowList,
4877
+ ``,
4878
+ `EXECUTION PLAN:`,
4879
+ `1. Call gitlab_get_flow_definition for ALL flows listed above in a SINGLE response (${flowMentions.length} tool calls at once).`,
4880
+ ` Parse each YAML to find what "context:goal" maps to (the "as" field in components).`,
4881
+ ``,
4882
+ `2. If the user's goal involves multiple resources (e.g., "for each MR"), list them using GitLab API tools.`,
4883
+ ``,
4884
+ `3. Call gitlab_execute_project_flow for EVERY flow+resource combination in a SINGLE response.`,
4885
+ ` For each call, set the goal to the EXACT value the flow expects (e.g., just "14" for merge_request_iid).`,
4886
+ ` project_id: "${projectPath}"`,
4887
+ ` You MUST emit ALL execute calls in ONE response \u2014 do NOT wait for one to finish before calling the next.`,
4888
+ ``,
4889
+ `4. Collect all workflow_ids from step 3. Call gitlab_get_workflow_status for ALL of them in a SINGLE response.`,
4890
+ ``,
4891
+ `5. Present a summary table: flow name, resource, status, URL (${projectUrl}/-/automate/agent-sessions/<id>).`,
4892
+ ``,
4893
+ `CRITICAL: In steps 1, 3, and 4 you MUST make multiple tool calls in the SAME response for parallel execution.`
4894
+ ].join("\n");
4895
+ const combinedText = [
4896
+ `IMPORTANT: You MUST call the Task tool RIGHT NOW with subagent_type "general" to dispatch all flows in parallel.`,
4897
+ `Do NOT call flow tools yourself. Do NOT dispatch multiple Task calls \u2014 use ONE.`,
4898
+ ``,
4899
+ `Call the Task tool with:`,
4900
+ ` subagent_type: "general"`,
4901
+ ` description: "Execute ${flowMentions.length} flows in parallel"`,
4902
+ ` prompt: ${JSON.stringify(batchPrompt)}`,
4903
+ ``,
4904
+ `Do not do anything else. Just call the Task tool with the above parameters.`
4905
+ ].join("\n");
4906
+ const firstIdx = flowMentions[0].idx;
4907
+ const original = output.parts[firstIdx];
4908
+ output.parts[firstIdx] = { ...original, type: "text", text: combinedText };
4909
+ delete output.parts[firstIdx].name;
4910
+ delete output.parts[firstIdx].source;
4911
+ for (let i = flowMentions.length - 1; i >= 1; i--) {
4912
+ indicesToRemove.push(flowMentions[i].idx);
4913
+ }
4914
+ for (const idx of [...new Set(indicesToRemove)].sort((a, b) => b - a)) {
4661
4915
  output.parts.splice(idx, 1);
4662
4916
  }
4663
4917
  },
@@ -4673,6 +4927,14 @@ var plugin = async (input) => {
4673
4927
  );
4674
4928
  }
4675
4929
  },
4930
+ "experimental.chat.system.transform": async (_input, output) => {
4931
+ if (flowAgents.size) {
4932
+ output.system.push(FLOW_DISPATCH_GUIDELINES);
4933
+ }
4934
+ if (authCache) {
4935
+ output.system.push(AGENT_CREATION_GUIDELINES);
4936
+ }
4937
+ },
4676
4938
  tool: {
4677
4939
  gitlab_execute_project_flow: (0, import_plugin.tool)({
4678
4940
  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.",
@@ -4771,6 +5033,134 @@ var plugin = async (input) => {
4771
5033
  }
4772
5034
  }
4773
5035
  }),
5036
+ gitlab_list_project_mcp_servers: (0, import_plugin.tool)({
5037
+ 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.",
5038
+ args: {
5039
+ project_id: z.string().describe('Project path (e.g., "gitlab-org/gitlab")')
5040
+ },
5041
+ execute: async (_args) => {
5042
+ const serverMap = /* @__PURE__ */ new Map();
5043
+ for (const agent of cachedAgents) {
5044
+ if (!agent.mcpServers?.length) continue;
5045
+ for (const server of agent.mcpServers) {
5046
+ const existing = serverMap.get(server.id);
5047
+ if (existing) {
5048
+ if (!existing.usedBy.includes(agent.name)) existing.usedBy.push(agent.name);
5049
+ } else {
5050
+ serverMap.set(server.id, { ...server, usedBy: [agent.name] });
5051
+ }
5052
+ }
5053
+ }
5054
+ const servers = [...serverMap.values()];
5055
+ if (!servers.length) {
5056
+ return "No MCP servers found for agents enabled in this project.";
5057
+ }
5058
+ return JSON.stringify(servers, null, 2);
5059
+ }
5060
+ }),
5061
+ gitlab_create_agent: (0, import_plugin.tool)({
5062
+ 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.",
5063
+ args: {
5064
+ project_id: z.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
5065
+ name: z.string().describe("Display name for the agent"),
5066
+ description: z.string().describe("Description of what the agent does"),
5067
+ public: z.boolean().describe("Whether the agent is publicly visible in the AI Catalog"),
5068
+ system_prompt: z.string().describe("System prompt that defines the agent's behavior"),
5069
+ user_prompt: z.string().optional().describe("User prompt template (optional)"),
5070
+ tools: z.array(z.string()).optional().describe(
5071
+ '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.'
5072
+ ),
5073
+ mcp_tools: z.array(z.string()).optional().describe("Array of MCP tool names to enable"),
5074
+ mcp_servers: z.array(z.string()).optional().describe(
5075
+ '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.'
5076
+ ),
5077
+ release: z.boolean().optional().describe("Whether to release the version immediately (default: false)"),
5078
+ confirmed: z.boolean().optional().describe(
5079
+ "Set to true only after the user has reviewed and confirmed all parameters. Omit or set false on first call."
5080
+ )
5081
+ },
5082
+ execute: async (args) => {
5083
+ if (!args.confirmed) {
5084
+ return [
5085
+ "STOP: Do not create the agent yet. You must ask the user to confirm the configuration first.",
5086
+ "",
5087
+ "Follow these steps NOW:",
5088
+ "1. Call gitlab_list_builtin_tools and gitlab_list_project_mcp_servers to discover options.",
5089
+ "2. Use the question tool to ask the user ALL 4 of these (in one call):",
5090
+ " - Agent name (suggest one, allow custom input)",
5091
+ " - Visibility: Public or Private",
5092
+ " - Tools: group by category (Search, Issues, MRs, Epics, Files, Git, CI/CD, Security, Audit, Planning, Wiki, API) as multi-select",
5093
+ " - MCP servers: multi-select from available servers",
5094
+ "3. Generate a system prompt and show it to the user for approval.",
5095
+ "4. Call gitlab_create_agent again with confirmed=true after the user approves."
5096
+ ].join("\n");
5097
+ }
5098
+ const auth = authCache ?? readAuth();
5099
+ if (!auth) throw new Error("Not authenticated");
5100
+ const result = await createAgent(auth.instanceUrl, auth.token, args.project_id, {
5101
+ name: args.name,
5102
+ description: args.description,
5103
+ public: args.public,
5104
+ systemPrompt: args.system_prompt,
5105
+ userPrompt: args.user_prompt,
5106
+ tools: args.tools,
5107
+ mcpTools: args.mcp_tools,
5108
+ mcpServers: args.mcp_servers,
5109
+ release: args.release
5110
+ });
5111
+ await refreshAgents();
5112
+ return JSON.stringify(result, null, 2);
5113
+ }
5114
+ }),
5115
+ gitlab_update_agent: (0, import_plugin.tool)({
5116
+ description: "Update an existing custom agent in the GitLab AI Catalog.\nOnly provided fields are updated; omitted fields remain unchanged.",
5117
+ args: {
5118
+ id: z.string().describe("Agent ID (numeric or full GID)"),
5119
+ name: z.string().optional().describe("New display name"),
5120
+ description: z.string().optional().describe("New description"),
5121
+ public: z.boolean().optional().describe("Whether publicly visible"),
5122
+ system_prompt: z.string().optional().describe("New system prompt"),
5123
+ user_prompt: z.string().optional().describe("New user prompt template"),
5124
+ tools: z.array(z.string()).optional().describe(
5125
+ 'New set of built-in tool Global IDs (full GIDs like "gid://gitlab/Ai::Catalog::BuiltInTool/1")'
5126
+ ),
5127
+ mcp_tools: z.array(z.string()).optional().describe("New set of MCP tool names"),
5128
+ mcp_servers: z.array(z.string()).optional().describe(
5129
+ 'New set of MCP server Global IDs (full GIDs like "gid://gitlab/Ai::Catalog::McpServer/1")'
5130
+ ),
5131
+ release: z.boolean().optional().describe("Whether to release the latest version"),
5132
+ version_bump: z.enum(["MAJOR", "MINOR", "PATCH"]).optional().describe("Version bump type")
5133
+ },
5134
+ execute: async (args) => {
5135
+ const auth = authCache ?? readAuth();
5136
+ if (!auth) throw new Error("Not authenticated");
5137
+ const result = await updateAgent(auth.instanceUrl, auth.token, args.id, {
5138
+ name: args.name,
5139
+ description: args.description,
5140
+ public: args.public,
5141
+ systemPrompt: args.system_prompt,
5142
+ userPrompt: args.user_prompt,
5143
+ tools: args.tools,
5144
+ mcpTools: args.mcp_tools,
5145
+ mcpServers: args.mcp_servers,
5146
+ release: args.release,
5147
+ versionBump: args.version_bump
5148
+ });
5149
+ await refreshAgents();
5150
+ return JSON.stringify(result, null, 2);
5151
+ }
5152
+ }),
5153
+ gitlab_list_builtin_tools: (0, import_plugin.tool)({
5154
+ 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.",
5155
+ args: {},
5156
+ execute: async () => {
5157
+ const auth = authCache ?? readAuth();
5158
+ if (!auth) throw new Error("Not authenticated");
5159
+ const tools = await listBuiltInTools(auth.instanceUrl, auth.token);
5160
+ if (!tools.length) return "No built-in tools available.";
5161
+ return JSON.stringify(tools, null, 2);
5162
+ }
5163
+ }),
4774
5164
  ...makeAgentFlowTools(
4775
5165
  z,
4776
5166
  () => authCache,