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/README.md +52 -25
- package/dist/index.cjs +453 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +453 -63
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
-
|
|
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
|
|
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
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
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
|
-
|
|
4643
|
-
|
|
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
|
-
|
|
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,
|