opencode-gitlab-dap 1.5.1 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -91,7 +91,26 @@ The plugin guides you through an interactive workflow:
91
91
 
92
92
  The `confirmed` parameter on `gitlab_create_agent` enforces the interactive workflow — the tool returns instructions instead of creating the agent when called without explicit confirmation.
93
93
 
94
- ### 17 DAP Tools
94
+ ### Custom Flow Creation
95
+
96
+ Design and create custom flows interactively:
97
+
98
+ ```
99
+ Create a flow that fetches MR data, analyzes the changes, and posts a summary comment
100
+ ```
101
+
102
+ The plugin provides a multi-round design workflow:
103
+
104
+ 1. `gitlab_design_flow` with `action="get_schema"` returns the flow YAML schema reference, two annotated examples, and step-by-step instructions
105
+ 2. The LLM asks the user about the flow's purpose, resource type, and steps
106
+ 3. The LLM proposes a component architecture for user approval
107
+ 4. The LLM generates the full YAML, validates it client-side against the `flow_v2` JSON schema via `gitlab_design_flow` with `action="validate"`
108
+ 5. After user confirmation, `gitlab_create_flow` with `confirmed=true` submits it to GitLab
109
+ 6. After creation, offers to enable the flow on the current project via `gitlab_enable_project_flow`
110
+
111
+ The vendored `flow_v2.json` schema from GitLab Rails powers client-side validation using `ajv`, catching errors before hitting the API.
112
+
113
+ ### 20 DAP Tools
95
114
 
96
115
  | Tool | Description |
97
116
  | --------------------------------- | ------------------------------------------------------- |
@@ -102,7 +121,10 @@ The `confirmed` parameter on `gitlab_create_agent` enforces the interactive work
102
121
  | `gitlab_disable_project_agent` | Disable an agent in a project |
103
122
  | `gitlab_create_agent` | Create a custom agent (interactive, confirmation-gated) |
104
123
  | `gitlab_update_agent` | Update an existing custom agent |
105
- | `gitlab_list_builtin_tools` | List available built-in tools for agent configuration |
124
+ | `gitlab_list_builtin_tools` | List available built-in tools for agent/flow config |
125
+ | `gitlab_design_flow` | Interactive flow design + YAML validation |
126
+ | `gitlab_create_flow` | Create a custom flow (confirmation-gated) |
127
+ | `gitlab_update_flow` | Update an existing custom flow |
106
128
  | `gitlab_list_flows` | Search flows in the global AI Catalog |
107
129
  | `gitlab_get_flow` | Get flow details by ID |
108
130
  | `gitlab_list_project_flows` | List flows enabled for a project |
@@ -124,6 +146,9 @@ Foundational flow definitions (from `gitlab-org/modelops/applied-ml/code-suggest
124
146
  are vendored and embedded at build time. This provides flow input schemas for
125
147
  foundational flows whose configs are not available via the GitLab API.
126
148
 
149
+ The `flow_v2.json` JSON schema is also vendored from GitLab Rails and bundled
150
+ inline for client-side YAML validation via `ajv`.
151
+
127
152
  ## Agent Types
128
153
 
129
154
  ### Foundational Agents
@@ -167,7 +192,7 @@ export GITLAB_INSTANCE_URL=https://your-instance.com
167
192
 
168
193
  ```bash
169
194
  npm install
170
- npm test # Run tests (62 tests)
195
+ npm test # Run tests (69 tests)
171
196
  npm run build # Build (auto-runs vendor:generate)
172
197
  npm run type-check
173
198
  ```
@@ -202,7 +227,7 @@ Plugin init
202
227
  ├─ config hook injects agents (primary) + flows (subagent with prompt)
203
228
  ├─ chat.message hook intercepts @flow → general subagent dispatch
204
229
  ├─ experimental.chat.system.transform injects flow dispatch + agent creation guidelines
205
- └─ tool hook 17 DAP tools
230
+ └─ tool hook 20 DAP tools
206
231
 
207
232
  Single flow execution (@FlowName goal):
208
233
  ├─ chat.message hook rewrites @mention → Task tool with general subagent
@@ -230,6 +255,17 @@ Agent creation (interactive):
230
255
  ├─ gitlab_create_agent called with confirmed=true → creates agent
231
256
  └─ gitlab_enable_project_agent optionally enable on project
232
257
 
258
+ Flow creation (interactive, multi-round):
259
+ ├─ gitlab_design_flow (get_schema) returns schema reference + examples + instructions
260
+ ├─ gitlab_list_builtin_tools discover available tool names for flow components
261
+ ├─ question tool ask user for name, visibility, purpose, steps
262
+ ├─ LLM proposes component architecture → user confirms
263
+ ├─ LLM generates flow YAML definition
264
+ ├─ gitlab_design_flow (validate) client-side validation against flow_v2.json via ajv
265
+ ├─ User confirms the generated YAML
266
+ ├─ gitlab_create_flow called with confirmed=true → creates flow
267
+ └─ gitlab_enable_project_flow optionally enable on project
268
+
233
269
  Enable/disable:
234
270
  └─ refreshAgents() clears cache, re-fetches, updates cfg.agent
235
271
  ```
@@ -0,0 +1,119 @@
1
+ // src/graphql.ts
2
+ async function gql(instanceUrl, token, query, variables) {
3
+ const res = await fetch(`${instanceUrl.replace(/\/$/, "")}/api/graphql`, {
4
+ method: "POST",
5
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
6
+ body: JSON.stringify({ query, variables })
7
+ });
8
+ const json = await res.json();
9
+ if (json.errors?.length) throw new Error(json.errors[0].message);
10
+ return json.data;
11
+ }
12
+
13
+ // src/mcp-servers.ts
14
+ var MCP_SERVERS_QUERY = `
15
+ query AiCatalogMcpServers($projectId: ProjectID!, $after: String) {
16
+ aiCatalogConfiguredItems(first: 50, projectId: $projectId, itemTypes: [AGENT], after: $after) {
17
+ pageInfo { hasNextPage endCursor }
18
+ nodes {
19
+ item {
20
+ id
21
+ name
22
+ latestVersion {
23
+ ... on AiCatalogAgentVersion {
24
+ mcpServers {
25
+ nodes { id name description url transport authType currentUserConnected }
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }`;
33
+ async function fetchMcpServers(instanceUrl, token, projectId, agents) {
34
+ try {
35
+ let after = null;
36
+ const serversByAgent = /* @__PURE__ */ new Map();
37
+ for (; ; ) {
38
+ const data = await gql(instanceUrl, token, MCP_SERVERS_QUERY, {
39
+ projectId,
40
+ ...after ? { after } : {}
41
+ });
42
+ const page = data?.aiCatalogConfiguredItems;
43
+ if (!page) break;
44
+ for (const node of page.nodes ?? []) {
45
+ const item = node.item;
46
+ const version = item?.latestVersion;
47
+ if (!version?.mcpServers?.nodes?.length) continue;
48
+ const servers = version.mcpServers.nodes.map((s) => ({
49
+ id: s.id,
50
+ name: s.name,
51
+ description: s.description ?? "",
52
+ url: s.url,
53
+ transport: s.transport,
54
+ authType: s.authType,
55
+ currentUserConnected: !!s.currentUserConnected
56
+ }));
57
+ serversByAgent.set(item.id, servers);
58
+ }
59
+ if (!page.pageInfo?.hasNextPage) break;
60
+ after = page.pageInfo.endCursor;
61
+ }
62
+ for (const agent of agents) {
63
+ const servers = serversByAgent.get(agent.identifier);
64
+ if (servers?.length) agent.mcpServers = servers;
65
+ }
66
+ } catch {
67
+ }
68
+ }
69
+ async function listMcpServerTools(server) {
70
+ try {
71
+ const res = await fetch(server.url, {
72
+ method: "POST",
73
+ headers: {
74
+ "Content-Type": "application/json",
75
+ Accept: "application/json, text/event-stream"
76
+ },
77
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} })
78
+ });
79
+ if (!res.ok) return [];
80
+ const data = await res.json();
81
+ const tools = data?.result?.tools;
82
+ if (!Array.isArray(tools)) return [];
83
+ return tools.map((t) => ({ name: t.name, description: t.description ?? "" }));
84
+ } catch {
85
+ return [];
86
+ }
87
+ }
88
+ async function discoverMcpToolNames(agents) {
89
+ const result = /* @__PURE__ */ new Map();
90
+ const seen = /* @__PURE__ */ new Set();
91
+ for (const agent of agents) {
92
+ if (!agent.mcpServers?.length) continue;
93
+ const agentToolNames = [];
94
+ for (const server of agent.mcpServers) {
95
+ if (seen.has(server.id)) {
96
+ const key2 = server.name.toLowerCase().replace(/[^a-z0-9]+/g, "_");
97
+ const cached = result.get(server.id);
98
+ if (cached) agentToolNames.push(...cached.map((t) => `${key2}_${t}`));
99
+ continue;
100
+ }
101
+ seen.add(server.id);
102
+ const tools = await listMcpServerTools(server);
103
+ const toolNames = tools.map((t) => t.name);
104
+ result.set(server.id, toolNames);
105
+ const key = server.name.toLowerCase().replace(/[^a-z0-9]+/g, "_");
106
+ agentToolNames.push(...toolNames.map((t) => `${key}_${t}`));
107
+ }
108
+ result.set(agent.identifier, agentToolNames);
109
+ }
110
+ return result;
111
+ }
112
+
113
+ export {
114
+ gql,
115
+ fetchMcpServers,
116
+ listMcpServerTools,
117
+ discoverMcpToolNames
118
+ };
119
+ //# sourceMappingURL=chunk-DPR6OUYG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/graphql.ts","../src/mcp-servers.ts"],"sourcesContent":["export async function gql(\n instanceUrl: string,\n token: string,\n query: string,\n variables: Record<string, unknown>\n): Promise<any> {\n const res = await fetch(`${instanceUrl.replace(/\\/$/, \"\")}/api/graphql`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${token}` },\n body: JSON.stringify({ query, variables }),\n });\n const json = (await res.json()) as any;\n if (json.errors?.length) throw new Error(json.errors[0].message);\n return json.data;\n}\n","import { gql } from \"./graphql\";\nimport type { CatalogAgent, McpServerInfo } from \"./types\";\n\nconst MCP_SERVERS_QUERY = `\nquery AiCatalogMcpServers($projectId: ProjectID!, $after: String) {\n aiCatalogConfiguredItems(first: 50, projectId: $projectId, itemTypes: [AGENT], after: $after) {\n pageInfo { hasNextPage endCursor }\n nodes {\n item {\n id\n name\n latestVersion {\n ... on AiCatalogAgentVersion {\n mcpServers {\n nodes { id name description url transport authType currentUserConnected }\n }\n }\n }\n }\n }\n }\n}`;\n\nexport async function fetchMcpServers(\n instanceUrl: string,\n token: string,\n projectId: string,\n agents: CatalogAgent[]\n): Promise<void> {\n try {\n let after: string | null = null;\n const serversByAgent = new Map<string, McpServerInfo[]>();\n\n for (;;) {\n const data = await gql(instanceUrl, token, MCP_SERVERS_QUERY, {\n projectId,\n ...(after ? { after } : {}),\n });\n const page = data?.aiCatalogConfiguredItems;\n if (!page) break;\n\n for (const node of page.nodes ?? []) {\n const item = node.item;\n const version = item?.latestVersion;\n if (!version?.mcpServers?.nodes?.length) continue;\n const servers: McpServerInfo[] = version.mcpServers.nodes.map((s: any) => ({\n id: s.id,\n name: s.name,\n description: s.description ?? \"\",\n url: s.url,\n transport: s.transport,\n authType: s.authType,\n currentUserConnected: !!s.currentUserConnected,\n }));\n serversByAgent.set(item.id, servers);\n }\n\n if (!page.pageInfo?.hasNextPage) break;\n after = page.pageInfo.endCursor;\n }\n\n for (const agent of agents) {\n const servers = serversByAgent.get(agent.identifier);\n if (servers?.length) agent.mcpServers = servers;\n }\n } catch {\n // MCP servers query not available — silently skip\n }\n}\n\nexport async function listMcpServerTools(\n server: McpServerInfo\n): Promise<Array<{ name: string; description: string }>> {\n try {\n const res = await fetch(server.url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json, text/event-stream\",\n },\n body: JSON.stringify({ jsonrpc: \"2.0\", id: 1, method: \"tools/list\", params: {} }),\n });\n if (!res.ok) return [];\n const data = await res.json();\n const tools = data?.result?.tools;\n if (!Array.isArray(tools)) return [];\n return tools.map((t: any) => ({ name: t.name, description: t.description ?? \"\" }));\n } catch {\n return [];\n }\n}\n\nexport async function discoverMcpToolNames(agents: CatalogAgent[]): Promise<Map<string, string[]>> {\n const result = new Map<string, string[]>();\n const seen = new Set<string>();\n\n for (const agent of agents) {\n if (!agent.mcpServers?.length) continue;\n const agentToolNames: string[] = [];\n for (const server of agent.mcpServers) {\n if (seen.has(server.id)) {\n const key = server.name.toLowerCase().replace(/[^a-z0-9]+/g, \"_\");\n const cached = result.get(server.id);\n if (cached) agentToolNames.push(...cached.map((t) => `${key}_${t}`));\n continue;\n }\n seen.add(server.id);\n const tools = await listMcpServerTools(server);\n const toolNames = tools.map((t) => t.name);\n result.set(server.id, toolNames);\n const key = server.name.toLowerCase().replace(/[^a-z0-9]+/g, \"_\");\n agentToolNames.push(...toolNames.map((t) => `${key}_${t}`));\n }\n result.set(agent.identifier, agentToolNames);\n }\n return result;\n}\n"],"mappings":"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","names":["key"]}