opencode-gitlab-dap 1.4.2 → 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/README.md +52 -25
- package/dist/index.cjs +469 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +469 -63
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
-
|
|
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,12 +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();
|
|
4660
|
+
const gitlabAgentNames = /* @__PURE__ */ new Set();
|
|
4661
|
+
let cachedAgents = [];
|
|
4438
4662
|
let cfgRef = null;
|
|
4439
4663
|
let baseModelIdRef;
|
|
4440
4664
|
async function load2() {
|
|
@@ -4469,6 +4693,7 @@ var plugin = async (input) => {
|
|
|
4469
4693
|
})
|
|
4470
4694
|
]);
|
|
4471
4695
|
flowAgents.clear();
|
|
4696
|
+
gitlabAgentNames.clear();
|
|
4472
4697
|
const currentNames = /* @__PURE__ */ new Set();
|
|
4473
4698
|
for (const agent of result.agents) {
|
|
4474
4699
|
const isFlow = agent.itemType === "FLOW";
|
|
@@ -4477,12 +4702,17 @@ var plugin = async (input) => {
|
|
|
4477
4702
|
currentNames.add(dName);
|
|
4478
4703
|
if (isFlow && agent.consumerId && projectPath) {
|
|
4479
4704
|
flowAgents.set(dName, agent);
|
|
4705
|
+
const rBaseUrl = authCache?.instanceUrl?.replace(/\/$/, "") ?? "https://gitlab.com";
|
|
4706
|
+
const rProjectUrl = `${rBaseUrl}/${projectPath}`;
|
|
4480
4707
|
cfgRef.agent[dName] = {
|
|
4481
4708
|
name: dName,
|
|
4482
4709
|
description: `[GitLab Flow] ${agent.description}`,
|
|
4483
|
-
mode: "subagent"
|
|
4710
|
+
mode: "subagent",
|
|
4711
|
+
prompt: buildFlowSubagentPrompt(agent, projectPath, rProjectUrl),
|
|
4712
|
+
permission: { "*": "allow" }
|
|
4484
4713
|
};
|
|
4485
4714
|
} else {
|
|
4715
|
+
gitlabAgentNames.add(dName);
|
|
4486
4716
|
cfgRef.agent[dName] = {
|
|
4487
4717
|
name: dName,
|
|
4488
4718
|
description: agent.foundational ? "[GitLab Foundational Agent]" : "[GitLab Custom Agent]",
|
|
@@ -4508,23 +4738,29 @@ var plugin = async (input) => {
|
|
|
4508
4738
|
async config(cfg) {
|
|
4509
4739
|
const result = await load2();
|
|
4510
4740
|
if (!result?.agents.length) return;
|
|
4741
|
+
cachedAgents = result.agents;
|
|
4511
4742
|
const baseModelId = resolveModelId(result.entry);
|
|
4512
4743
|
cfg.agent ??= {};
|
|
4513
4744
|
cfgRef = cfg;
|
|
4514
4745
|
baseModelIdRef = baseModelId;
|
|
4746
|
+
const cfgBaseUrl = authCache?.instanceUrl?.replace(/\/$/, "") ?? "https://gitlab.com";
|
|
4515
4747
|
for (const agent of result.agents) {
|
|
4516
4748
|
const isFlow = agent.itemType === "FLOW";
|
|
4517
4749
|
const icon = agent.foundational ? " \u{1F98A}" : "";
|
|
4518
4750
|
if (isFlow && agent.consumerId && projectPath) {
|
|
4519
4751
|
const displayName = `${agent.name}${icon}`;
|
|
4520
4752
|
flowAgents.set(displayName, agent);
|
|
4753
|
+
const pUrl = `${cfgBaseUrl}/${projectPath}`;
|
|
4521
4754
|
cfg.agent[displayName] = {
|
|
4522
4755
|
name: displayName,
|
|
4523
4756
|
description: `[GitLab Flow] ${agent.description}`,
|
|
4524
|
-
mode: "subagent"
|
|
4757
|
+
mode: "subagent",
|
|
4758
|
+
prompt: buildFlowSubagentPrompt(agent, projectPath, pUrl),
|
|
4759
|
+
permission: { "*": "allow" }
|
|
4525
4760
|
};
|
|
4526
4761
|
} else {
|
|
4527
4762
|
const displayName = `${agent.name}${icon}`;
|
|
4763
|
+
gitlabAgentNames.add(displayName);
|
|
4528
4764
|
cfg.agent[displayName] = {
|
|
4529
4765
|
name: displayName,
|
|
4530
4766
|
description: agent.foundational ? "[GitLab Foundational Agent]" : "[GitLab Custom Agent]",
|
|
@@ -4543,86 +4779,128 @@ var plugin = async (input) => {
|
|
|
4543
4779
|
},
|
|
4544
4780
|
"chat.message": async (_input, output) => {
|
|
4545
4781
|
const indicesToRemove = [];
|
|
4546
|
-
const
|
|
4782
|
+
const flowMentions = [];
|
|
4547
4783
|
for (let i = 0; i < output.parts.length; i++) {
|
|
4548
4784
|
const part = output.parts[i];
|
|
4549
4785
|
if (part.type !== "agent") continue;
|
|
4550
4786
|
const flow = flowAgents.get(part.name);
|
|
4551
4787
|
if (!flow || !flow.consumerId || !projectPath) continue;
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
`- Look at the user's goal for hints (MR numbers, pipeline IDs, branch names, URLs)`,
|
|
4572
|
-
`- For URL fields: construct full GitLab URLs using base ${projectUrl}`,
|
|
4573
|
-
`- For branch fields: look up from MRs, pipelines, or project default branch`,
|
|
4574
|
-
`- For IDs: extract from the user's message or look up via GitLab API`,
|
|
4575
|
-
`- If an input cannot be determined, ask yourself what makes sense given the context`,
|
|
4576
|
-
`Print each resolved input value before proceeding.`,
|
|
4577
|
-
``,
|
|
4578
|
-
`STEP 3 - EXECUTE THE FLOW:`,
|
|
4579
|
-
`Call gitlab_execute_project_flow with:`,
|
|
4580
|
-
`- project_id: "${projectPath}"`,
|
|
4581
|
-
`- consumer_id: ${flow.consumerId}`,
|
|
4582
|
-
`- goal: the user's goal with any relevant URLs/context appended`,
|
|
4583
|
-
`- additional_context: JSON array of inputs from step 2, each entry: {"Category":"<category>","Content":"{\\"field\\":\\"value\\"}"}`,
|
|
4584
|
-
` Example: [{"Category":"merge_request","Content":"{\\"url\\":\\"${projectUrl}/-/merge_requests/12\\"}"}]`,
|
|
4585
|
-
``,
|
|
4586
|
-
`STEP 4 - CONFIRM FLOW STARTED:`,
|
|
4587
|
-
`Call gitlab_get_workflow_status with the returned workflow_id.`,
|
|
4588
|
-
`The tool blocks automatically while status is CREATED (up to 2 minutes).`,
|
|
4589
|
-
`Once it returns, report:`,
|
|
4590
|
-
`- The workflow status (RUNNING, FINISHED, FAILED, etc.)`,
|
|
4591
|
-
`- The workflow URL: ${projectUrl}/-/automate/agent-sessions/<workflow_id>`,
|
|
4592
|
-
`- A brief summary of what was executed`,
|
|
4593
|
-
`Then stop. Do NOT keep polling. The user can follow the flow execution in the GitLab UI.`
|
|
4594
|
-
].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}"`;
|
|
4595
4807
|
const resultText = [
|
|
4596
4808
|
`IMPORTANT: You MUST call the Task tool RIGHT NOW to dispatch a subagent. Do NOT execute these steps yourself.`,
|
|
4597
4809
|
``,
|
|
4598
4810
|
`Call the Task tool with:`,
|
|
4811
|
+
` subagent_type: "general"`,
|
|
4599
4812
|
` description: "Execute ${flow.name} flow"`,
|
|
4600
4813
|
` prompt: ${JSON.stringify(subagentPrompt)}`,
|
|
4601
4814
|
``,
|
|
4602
4815
|
`Do not do anything else. Just call the Task tool with the above parameters.`
|
|
4603
4816
|
].join("\n");
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
const next = output.parts[i + 1];
|
|
4607
|
-
if (next.type === "text" && next.synthetic && next.text?.includes("call the task tool with subagent")) {
|
|
4608
|
-
indicesToRemove.push(i + 1);
|
|
4609
|
-
}
|
|
4610
|
-
}
|
|
4611
|
-
}
|
|
4612
|
-
for (const { idx, text } of replacements) {
|
|
4613
|
-
const original = output.parts[idx];
|
|
4614
|
-
output.parts[idx] = {
|
|
4615
|
-
...original,
|
|
4616
|
-
type: "text",
|
|
4617
|
-
text
|
|
4618
|
-
};
|
|
4817
|
+
const original2 = output.parts[idx];
|
|
4818
|
+
output.parts[idx] = { ...original2, type: "text", text: resultText };
|
|
4619
4819
|
delete output.parts[idx].name;
|
|
4620
4820
|
delete output.parts[idx].source;
|
|
4821
|
+
for (const rmIdx of indicesToRemove.reverse()) {
|
|
4822
|
+
output.parts.splice(rmIdx, 1);
|
|
4823
|
+
}
|
|
4824
|
+
return;
|
|
4825
|
+
}
|
|
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);
|
|
4621
4879
|
}
|
|
4622
|
-
for (const idx of indicesToRemove.
|
|
4880
|
+
for (const idx of [...new Set(indicesToRemove)].sort((a, b) => b - a)) {
|
|
4623
4881
|
output.parts.splice(idx, 1);
|
|
4624
4882
|
}
|
|
4625
4883
|
},
|
|
4884
|
+
"chat.params": async (input2, _output) => {
|
|
4885
|
+
if (!gitlabAgentNames.has(input2.agent)) return;
|
|
4886
|
+
const model = input2.model;
|
|
4887
|
+
const modelId = model?.modelID ?? model?.id ?? "";
|
|
4888
|
+
const isDWS = modelId.includes("duo-workflow");
|
|
4889
|
+
if (!isDWS) {
|
|
4890
|
+
const name = model?.name ?? modelId ?? "unknown";
|
|
4891
|
+
throw new Error(
|
|
4892
|
+
`GitLab agent "${input2.agent}" requires an Agent Platform model but the current model is "${name}". Please switch to an Agent Platform model (duo-workflow-*) in the model picker to use GitLab agents.`
|
|
4893
|
+
);
|
|
4894
|
+
}
|
|
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
|
+
},
|
|
4626
4904
|
tool: {
|
|
4627
4905
|
gitlab_execute_project_flow: tool({
|
|
4628
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.",
|
|
@@ -4721,6 +4999,134 @@ var plugin = async (input) => {
|
|
|
4721
4999
|
}
|
|
4722
5000
|
}
|
|
4723
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
|
+
}),
|
|
4724
5130
|
...makeAgentFlowTools(
|
|
4725
5131
|
z,
|
|
4726
5132
|
() => authCache,
|