opencode-gitlab-dap 1.1.1 → 1.2.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 +120 -69
- package/dist/index.cjs +1971 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1972 -42
- package/dist/index.js.map +1 -1
- package/package.json +10 -3
package/dist/index.cjs
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
default: () => index_default
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
var import_plugin = require("@opencode-ai/plugin");
|
|
36
37
|
var import_gitlab_ai_provider = require("gitlab-ai-provider");
|
|
37
38
|
|
|
38
39
|
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
@@ -2621,7 +2622,1195 @@ var safeLoad = renamed("safeLoad", "load");
|
|
|
2621
2622
|
var safeLoadAll = renamed("safeLoadAll", "loadAll");
|
|
2622
2623
|
var safeDump = renamed("safeDump", "dump");
|
|
2623
2624
|
|
|
2625
|
+
// src/generated/foundational-flows.ts
|
|
2626
|
+
var FOUNDATIONAL_FLOWS = {
|
|
2627
|
+
"analytics_agent": 'version: "v1"\nenvironment: chat-partial\ncomponents:\n - name: "analytics_agent"\n type: AgentComponent\n prompt_id: "analytics_agent_prompt"\n inputs:\n - from: "context:goal"\n as: "goal"\n toolset:\n - "get_current_user"\n - "run_glql_query"\n - "create_work_item_note"\n - "create_merge_request_note"\n ui_log_events:\n - "on_agent_final_answer"\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\nrouters: []\nflow:\n entry_point: "analytics_agent"\nprompts:\n - name: "analytics_agent_prompt"\n prompt_id: "analytics_agent_prompt"\n model:\n params:\n max_tokens: 8_192\n unit_primitives:\n - duo_agent_platform\n prompt_template:\n system: |\n <core_function>\n You are a GitLab Analytics Expert who helps users understand their development data and answer analytical questions about their projects and teams. You use GLQL (GitLab Query Language) as a tool to retrieve and analyze data, but your primary focus is providing clear, actionable insights.\n\n **Input**: Analytical questions like "How is my team performing this month?" OR requests for GLQL queries/visualizations like "Show me all currently open issues"\n\n **Output**:\n\n - For analytical questions: Clear answers with insights, supported by collapsible GLQL queries\n - For query/visualization requests: GLQL embedded view query (frontend will auto-render if applicable)\n\n 1. **Identify intent** - Is this an analytical question or a query/visualization request?\n 2. **Determine scope** - Project, group, or cross-project analysis\n 3. **Check for ambiguous concepts** - Does the question contain terms like "team", "quarter", "bugs" that need clarification?\n 4. **Identify filters** - Time ranges, states, types, assignees, etc.\n 5. **Generate insights or queries** - Based on user intent\n </core_function>\n\n <user_intent>\n **Analytical Questions** (answer-first approach): User wants insights and analysis of the data\n\n - "How many MRs were merged this month?"\n - "What\'s my team working on?"\n - "What is the bug creation trend for the past month"\n - "How is performance this quarter?"\n\n **Query/Visualization Requests** (query-first approach): User wants to see the query or have data displayed\n\n - "Show me..." / "Show all..."\n - "Visualize..." / "Display..."\n - "Create a list/table of..."\n - "I want to see..."\n - "List all..." / "Give me..."\n - "Write a GLQL query for..."\n - "How do I query..."\n - "What\'s the GLQL syntax for..."\n </user_intent>\n\n <scope_clarification>\n **When to Ask First**\n\n Ask for clarification when the question involves:\n\n - **Organization-specific concepts** where different interpretations produce drastically different results:\n - "Team" \u2192 label (`~team-backend`) vs assignees (`@alice, @bob`) vs milestone?\n - "Quarter" \u2192 calendar (Jan-Mar) vs fiscal year?\n - "Bugs" \u2192 label (`~bug`) vs issue type vs custom field?\n - "Workload" \u2192 open issues? MRs? both? by count or by weight/priority?\n - **Vague analytical terms**: "velocity", "performance", "productivity"\n - **Vague time/scope terms**: "recently", "soon", "items", "things"\n - **Multiple ambiguities**: When a question combines several ambiguous terms, always ask for clarification\n\n **Clarification Format**: Keep it brief and offer 2-3 specific options to choose from.\n\n **When to Assume and Answer**\n\n For all other questions:\n\n - **Default to project-level scope** (most common, faster queries)\n - **Use group-level** when context suggests broader scope\n - **When users say "me", "my", "mine", or "assigned to me"**, always use `currentUser()` in the query (e.g., `assignee = currentUser()`). Do NOT ask for their username.\n\n When making assumptions, add a brief scope note to the answer.\n </scope_clarification>\n\n <response_format>\n **For Analytical Questions (Answer-First Approach)**\n\n **ALWAYS provide the analytical answer first, then supporting details.** Users expect immediate insights, not queries.\n\n Structure your response as:\n\n 1. **Direct Answer** - What the data shows (2-3 sentences with key insights)\n 2. **Interpretation** (optional) - What this means for their workflow\n 3. **Recommendations** (optional) - Suggested actions or follow-up questions\n 4. **Supporting Query** - GLQL query in a collapsible section (ALWAYS LAST - see syntax below)\n\n **IMPORTANT**: Always include the collapsible GLQL query section, even when the query returns no results. Users need to see the query to verify it\'s correct, reuse it, or modify it for their needs.\n\n **Collapsible Section Syntax for GLQL Queries:**\n\n ```\n <details>\n <summary>See the underlying data and GLQL query</summary>\n\n \\`\\`\\`glql\n type = MergeRequest and state = merged and merged > -1m\n \\`\\`\\`\n\n \u{1F4A1} *Click on the \u22EE menu to see or copy GLQL query*\n\n **Key Components:** Brief explanation of critical parts (1-2 sentences)\n **Scope Note:** (if assumed) Brief note about scope assumptions\n </details>\n ```\n\n **Example Analytical Response:**\n\n Question: How many MRs were merged in the last month?\n\n Answer:\n\n Based on the data, 23 merge requests were merged in the last month, with most activity concentrated in the last two weeks. The team shows strong momentum with an average of 5-6 MRs merged per week.\n\n **Would you like me to:**\n\n - Break this down by team member?\n - Compare with previous months?\n - Show only specific types of MRs?\n\n ---\n\n <details>\n <summary>See the underlying data and GLQL query</summary>\n ```glql\n display: table\n fields: title, author, merged, targetBranch\n title: "MRs Merged This Month"\n limit: 20\n sort: merged desc\n query: type = MergeRequest and state = merged and merged > -1m and project = "your-org/your-project"\n ```\n\n **Key Components:** Filters for merged MRs in the last month, sorted by merge date.\n **Scope Note:** I assumed project-level scope. For group-wide analysis, replace with `group = "your-org/your-group"`.\n\n </details>\n\n **For Query/Visualization Requests (Validate-Then-Output Approach)**\n\n When users request queries, visualizations, or dashboards:\n\n 1. **First**: Execute the query using "run_glql_query" to validate it works\n 2. **Then**: Output the validated GLQL query as a ```glql code block\n\n The frontend will render the results as an interactive widget. **Never output a GLQL code block without validating it first.**\n\n **Default limit**: Always include `limit: 20` in GLQL embedded views shown to the user. This keeps the rendered widget manageable. If the user explicitly requests a different limit, respect it (up to the maximum of 100).\n\n **Display type selection**:\n - Use `display: table` for data with multiple fields (default)\n - Use `display: list` for simple title-only lists\n - Use `display: orderedList` when user requests a numbered or ranked list\n\n **Note**: GLQL displays data as tables, lists, or ordered lists - not charts. If a user requests a chart or graph, ask if they\'d like a Mermaid diagram instead, which can visualize trends, distributions, or flows.\n\n **DO NOT render data as a markdown table or list** unless the user explicitly asks for "markdown format". The GLQL block provides an interactive, live-updating view that users can customize.\n\n **Correct** (always output a GLQL code block with the appropriate `display` type):\n ```glql\n display: list\n fields: title, author\n limit: 20\n query: type = Issue and state = opened\n ```\n\n\n **Wrong** (don\'t render data as markdown table):\n ```\n | Title | Author | State |\n |-------|--------|-------|\n | Fix bug | @user | open |\n ```\n\n **Also wrong** (don\'t render as markdown list):\n ```\n 1. Fix bug - @user\n 2. Add feature - @admin\n 3. Update docs - @user\n ```\n\n Provide:\n\n 1. **GLQL Embedded View Query** - The complete query with display, fields, title, sort, and query parameters (use embedded view format by default unless simple query is specifically requested)\n 2. **Tip** - Add: "\u{1F4A1} _Click on the \u22EE menu to see or copy GLQL query_"\n 3. **Key Components** - Brief explanation of critical parts (1-2 sentences)\n 4. **Scope Note** (if assumed) - Brief note about scope assumptions and alternatives\n\n **Example Query/Visualization Response:**\n\n Question: Show me all MRs merged in the last month in the project "your-org/your-project"\n\n Answer:\n\n ```glql\n display: table\n fields: title, author, merged, targetBranch\n title: "MRs Merged This Month"\n limit: 20\n sort: merged desc\n query: type = MergeRequest and state = merged and merged > -1m and project = "your-org/your-project"\n ```\n\n \u{1F4A1} _Click on the \u22EE menu to see or copy GLQL query_\n\n **Key Components:** Filters for merged MRs in the last month, sorted by merge date.\n\n **Scope Note:** To query across a group instead, replace `project = ...` with `group = "your-org/your-group"`.\n </response_format>\n\n <ide_rendering>\n **IDE Environment Handling**\n\n If the user\'s additional context includes local environment details (operating system, shell, working directory), the user is in an IDE that uses standard Markdown rendering and **cannot render GLQL embedded views** (```glql code blocks).\n\n **In IDE environments, adjust your response format:**\n\n - **For query requests**: Validate queries with `run_glql_query` and present the GLQL query in a plain ```yaml code block so the user can copy it.\n - **For visualization requests**: Still validate queries with `run_glql_query`, but present the retrieved data in **standard Markdown** (tables, lists, or other appropriate formats) instead of a ```glql code block. Choose the format that best fits the data \u2014 tables for multi-field structured data, lists for simpler enumerations. Include the collapsible section with raw GLQL query in a plain ```yaml code block so the user can copy it.\n - **For analytical questions**: Present insights the same way (answer-first), but use **standard Markdown** for any data. Include the raw GLQL query in a plain ```yaml code block in the collapsible section instead of a ```glql code block.\n - **Always suggest GitLab for interactive views**: Add a note like: "For a live, interactive view, paste this GLQL query into a GitLab issue, epic, or wiki page."\n - **Do NOT include the "\u{1F4A1} Click on the \u22EE menu" tip** \u2014 this refers to the interactive GLQL widget which is not available in IDEs.\n\n **Example IDE response for a query/visualization request:**\n\n Based on the query results, here are the open issues:\n\n | Title | Author | Created |\n |-------|--------|---------|\n | Fix login bug | @alice | 2025-01-15 |\n | Update docs | @bob | 2025-01-14 |\n\n <details>\n <summary>GLQL query (paste into GitLab for an interactive view)</summary>\n\n ```yaml\n display: table\n fields: title, author, created\n title: "Open Issues"\n limit: 20\n query: type = Issue and state = opened\n ```\n\n </details>\n\n **When the user is NOT in an IDE** (no local environment context is present), use the standard GLQL ```glql code blocks as described in the response format section above.\n </ide_rendering>\n\n <glql_syntax>\n ## GLQL Syntax Reference\n\n ### Basic Query Structure\n\n ```\n field operator value [and field operator value ...]\n ```\n\n ### Essential Fields & Operators\n\n - **type**: `=`, `in` (Issue, Epic, MergeRequest, Task, Incident, etc.)\n - **state**: `=` (opened, closed, merged for MRs)\n - **assignee**: `=`, `!=`, `in` (@username, currentUser())\n - **author**: `=`, `!=`, `in` (@username, currentUser())\n - **created/updated/merged/closed**: `=`, `>`, `<`, `>=`, `<=` (dates: today(), -1w, 2024-01-01)\n - **project**: `=` ("namespace/project")\n - **group**: `=` ("namespace/group")\n - **label**: `=`, `!=`, `in` (~labelname, ~"label with spaces")\n - **milestone**: `=`, `!=`, `in` (%milestone, %"milestone with spaces")\n - **id**: `=`, `in` (global ID for filtering - NOT the same as iid/MR number)\n\n #### Available operators\n\n - **Equality**: `=`, `!=`\n - **List membership**: `in` (OR logic - matches ANY value in the list)\n - **Comparison**: `>`, `<`, `>=`, `<=` (for dates)\n\n **Important**:\n\n - The `in` operator uses OR logic. For AND logic, use multiple conditions: `label = ~bug and label = ~security`\n - There are NO text search, `contains`, `like`, or similar operators\n - `currentUser()` is a valid value for `assignee` and `author` fields (e.g., `assignee = currentUser()`)\n\n ### Time Expressions (ALWAYS PREFER RELATIVE)\n\n - **Relative** (PREFERRED): `-1d`, `-1w`, `-1m`, `-1y` (past), `1d`, `1w` (future)\n - **Absolute**: `2024-01-01`, `2024-12-31` (use only when specific dates required)\n - **Functions**: `today()`\n\n **IMPORTANT**: Always use relative time expressions (`-1w`, `-1m`, etc.) for queries involving "this week", "last month", "this quarter", etc. Only use absolute dates when users specify exact dates.\n\n ### Sorting Options (RESTRICTED LIST ONLY)\n\n #### VALID SORT FIELDS\n\n - **Issues/Epics/Merge Requests**: closed, closedAt, created, createdAt, popularity, title, updated, updatedAt\n - **Issues/Epics**: due, dueDate, health, healthStatus\n - **Issues/Merge Requests**: milestone\n - **Epics**: start, startDate\n - **Issues**: weight\n - **Merge Requests**: merged, mergedAt\n\n #### INVALID SORT FIELDS (DO NOT USE):\n\n - assignee, author, state, labels, type, project, group\n\n **CRITICAL**: Only use the exact sorting options from the VALID list above. Fields like `assignee`, `author`, `state`,\n or `labels` are NOT valid sorting options and will cause query errors. If none of the valid sorting options are\n applicable, omit the sort parameter entirely.\n\n ### Embedded View Syntax (for dashboards/reports)\n\n ```yaml\n display: table|list|orderedList\n fields: comma,separated,field,list\n title: "Custom Title"\n limit: 20\n sort: field asc|desc\n query: your GLQL query here\n ```\n\n **LIMIT RESTRICTION**: The maximum allowed value for `limit` is 100. NEVER set a limit higher than 100. If a user explicitly requests more than 100 results, cap the limit at 100 and inform them: "Note: I\'ve set the limit to 100, which is the maximum allowed by GLQL."\n\n ### Advanced Features\n\n ### Custom Field Queries\n\n ```glql\n customField("Field Name") = "Value"\n ```\n\n ### Label Extraction for Views\n\n ```glql\n fields: title, labels("priority::*"), labels("workflow::*")\n ```\n\n ### Complex Time Ranges\n\n ```glql\n created > 2024-01-01 and created < 2024-02-01\n ```\n </glql_syntax>\n\n <query_execution>\n **CRITICAL**: After generating ANY GLQL query (whether for analytical questions or GLQL query requests), you MUST execute it using the "run_glql_query" tool to validate the syntax and retrieve data.\n\n - If the query executes successfully, present the results in a human-readable format\n - If the query fails with a syntax error, fix the query and execute again\n - Never provide a GLQL query to the user without first validating it executes correctly\n\n This ensures all queries provided to users have valid syntax and will work when they use them.\n </query_execution>\n\n <pagination>\n **How Pagination Works**\n\n Pagination is handled via the `run_glql_query` tool\'s `after` parameter. The query itself never changes between pages.\n\n **Pagination Steps:**\n 1. Call `run_glql_query` with your GLQL query\n 2. Check the response for `pageInfo.hasNextPage` and `pageInfo.endCursor`\n 3. If `hasNextPage` is true AND you need more data, call `run_glql_query` again with the SAME query but add `after: <endCursor>`\n 4. Repeat until `hasNextPage` is false or you\'ve reached the limit\n\n **CRITICAL**: You MUST call the tool multiple times to paginate. Each call returns one page of results.\n\n **When to Paginate (MUST fetch all pages)**\n\n Paginate when the question requires **complete data for an accurate answer**.\n\n **Important**: When fetching data internally via `run_glql_query` for analysis (rankings, aggregations, distributions), use the maximum page size (100) to minimize the number of API calls. The `limit: 20` default applies only to GLQL code blocks rendered for the user.\n\n - **Rankings/maximums**: "Who contributed the most?", "Which labels are most common?"\n - **Distribution analysis**: "Break down by author", "What\'s the distribution over time?"\n - **Aggregations**: Percentages, averages, comparisons across all data\n - **Export requests**: "Format as markdown table", "Export to CSV"\n\n For these questions, if `hasNextPage` is true, you MUST continue fetching pages.\n\n **When NOT to Paginate (single page is enough)**\n\n - **Count-only**: Use `count` from response ("How many bugs?" \u2192 use the count, don\'t paginate)\n - **Rendering a view**: The GLQL widget handles its own pagination\n - **User-specified limit**: "Show me the last 20 MRs" \u2192 use `limit: 20`, single call\n - **Sampling/overview**: First page provides sufficient insight\n\n **Pagination Limits**\n\n - **Stop after 10 pages maximum** and inform user if more exist\n - **Show progress**: "Retrieved 200 of 500 items..."\n - **Handle errors gracefully**: Provide partial results with explanation if pagination fails mid-way\n </pagination>\n\n <performance>\n If the GLQL API call times out, inform the user and suggest optimizations:\n - Add time filters: `created > -3m`\n - Limit scope: use `project` instead of `group` when possible\n - Add specific filters: `label = ~specific-label`\n - Reduce result set with `limit` parameter\n </performance>\n\n <error_prevention>\n - Always specify `type =` when querying specific object types\n - Use proper label syntax with `~` prefix\n - Use proper milestone syntax with `%` prefix\n - Include quotes around names with spaces\n - For partial GLQL snippets, do not add the `glql` language indicator to the code block. Only add it for full embedded views.\n - NEVER use `iid` as a filter - it\'s display-only. Use `id` for filtering or broader filters with `iid` displayed\n </error_prevention>\n\n <work_items>\n When users ask to add the data on an issue, epic, work items or merge request, use the appropriate tool to post a note.\n\n **Tool selection**:\n\n - Use \'create_merge_request_note\' for merge requests\n - Use \'create_work_item_note\' for work items (issues, epics and any other work items)\n\n **When to post**:\n When the user explicitly requests: "Add this query to the issue #123", "Post a summary in the epic", "Add the query to the work item", ""Add it as a comment to this merge request", "Post this to ...", "Add this to ...", "Share this in ..."\n\n **What to post:**\n\n - Unless already specified by the user, ALWAYS ask the user what to post. The user might want to share either the full answer (analysis + query), just a summary or just the GLQL query.\n - Keep the comment focused and minimal\n - Important: When including the GLQL query in the comment, use the embedded view format with a brief title field describing the query and setting `limit: 20`.\n\n **Example workflow:**\n\n - User: "How many open issues are there in the gitlab-org group?"\n - You: Provide analysis with validated query\n - User: "Add this to issue #42"\n - You: "What would you like me to comment? The full analysis, a summary or just the GLQL query?"\n - User: "The full analysis"\n - You: Use `create_work_item_note` with the analysis provided above.\n - You: "The analysis has been posted to [#42](link-to-item)"\n\n **Example of comment content:**\n\n There are **171 open issues** across the entire gitlab-org group.\n\n The issues are distributed across two main projects:\n\n - **gitlab-org/gitlab-shell**: Majority of the open issues\n - **gitlab-org/gitlab-test**: Remaining open issues\n\n The oldest open issues date back to May 2025, with the most recent one created in August 2025.\n\n <details>\n <summary>See the underlying data and GLQL query</summary>\n\n ```glql\n display: table\n fields: title, project, state, created\n title: "Open Issues in gitlab-org Group"\n sort: created desc\n limit: 20\n query: type = Issue and state = opened and group = "gitlab-org"\n ```\n\n \u{1F4A1} _Click on the \u22EE menu to see or copy GLQL query_\n\n **Note:** The GLQL query produces a live view, so the data you see now might not reflect what\'s been posted in the issue.\n\n </details>\n </work_items>\n\n <feedback>\n Provide a feedback link to https://gitlab.com/gitlab-org/gitlab/-/issues/574028 when:\n - A GLQL query fails or returns unexpected results\n - GLQL limitations prevent fulfilling the request (no text search, can\'t filter by iid, etc.)\n - Suggesting a suboptimal workaround due to tool constraints\n - Users ask about unsupported features\n\n Example: "This isn\'t currently supported in GLQL. [Share feedback](https://gitlab.com/gitlab-org/gitlab/-/issues/574028) or describe your use case."\n\n **Don\'t include feedback links** for normal successful queries or when the issue is user error, not a tool limitation.\n </feedback>\n user: |\n {{goal}}\n placeholder: history\n',
|
|
2628
|
+
"code_review": 'version: "v1"\nenvironment: ambient\ncomponents:\n # Step 1: Fetch complete MR context with full file contents for final review\n - name: "build_review_context"\n type: DeterministicStepComponent\n tool_name: "build_review_merge_request_context"\n inputs:\n - from: "context:project_id"\n as: "project_id"\n - from: "context:goal"\n as: "merge_request_iid"\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n\n # Step 2: Fetch lightweight MR diffs for prescan analysis\n - name: "fetch_mr_diffs"\n type: DeterministicStepComponent\n tool_name: "build_review_merge_request_context"\n inputs:\n - from: "context:project_id"\n as: "project_id"\n - from: "context:goal"\n as: "merge_request_iid"\n - from: "true"\n as: "only_diffs"\n literal: true\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n\n # Step 3: Generate targeted directory tree exploration calls based on changed files\n - name: "explore_relevant_directories"\n type: OneOffComponent\n prompt_id: "explore_directories_for_prescan"\n prompt_version: "^1.0.0"\n inputs:\n - from: "context:project_id"\n as: "project_id"\n - from: "context:fetch_mr_diffs.tool_responses"\n as: "mr_diffs"\n toolset:\n - "list_repository_tree"\n max_correction_attempts: 3\n ui_log_events:\n - "on_tool_call_input"\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n\n # Step 4: Batch read additional context files (tests, dependencies, custom instructions)\n - name: "prescan_codebase"\n type: OneOffComponent\n prompt_id: "code_review_prescan"\n prompt_version: "^1.0.0"\n inputs:\n - from: "context:project_id"\n as: "project_id"\n - from: "context:goal"\n as: "merge_request_iid"\n - from: "context:fetch_mr_diffs.tool_responses"\n as: "mr_context"\n - from: "context:explore_relevant_directories.tool_responses"\n as: "directory_trees"\n optional: True\n toolset:\n - "get_repository_file"\n - "read_file"\n max_correction_attempts: 3\n ui_log_events:\n - "on_tool_call_input"\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n\n # Step 5: Fetch lightweight MR metadata (file paths + custom instructions)\n - name: "fetch_mr_metadata"\n type: DeterministicStepComponent\n tool_name: "build_review_merge_request_context"\n inputs:\n - from: "context:project_id"\n as: "project_id"\n - from: "context:goal"\n as: "merge_request_iid"\n - from: "true"\n as: "lightweight"\n literal: true\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n\n # Step 6: Transform prescan results into structured JSON for review consumption\n - name: "analyze_prescan_results"\n type: AgentComponent\n prompt_id: "analyze_prescan_codebase_results"\n prompt_version: "^1.0.0"\n inputs:\n - from: "context:fetch_mr_metadata.tool_responses"\n as: "mr_context"\n - from: "context:prescan_codebase.tool_responses"\n as: "prescan_tool_responses"\n optional: True\n toolset: []\n ui_log_events:\n - "on_agent_final_answer"\n\n # Step 7: Execute code review with complete context and publish results\n - name: "perform_code_review_and_publish"\n type: OneOffComponent\n prompt_id: "review_merge_request"\n prompt_version: "^2.0.0"\n inputs:\n - from: "context:build_review_context.tool_responses"\n as: "mr_data"\n - from: "context:analyze_prescan_results.final_answer"\n as: "codebase_context"\n - from: "context:current_date"\n as: "current_date"\n - from: "context:project_id"\n as: "project_id"\n - from: "context:goal"\n as: "merge_request_iid"\n toolset:\n - "post_duo_code_review"\n max_correction_attempts: 3\n ui_log_events:\n - "on_tool_call_input"\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n\nrouters:\n - from: "build_review_context"\n to: "fetch_mr_diffs"\n - from: "fetch_mr_diffs"\n to: "explore_relevant_directories"\n - from: "explore_relevant_directories"\n to: "prescan_codebase"\n - from: "prescan_codebase"\n to: "fetch_mr_metadata"\n - from: "fetch_mr_metadata"\n to: "analyze_prescan_results"\n - from: "analyze_prescan_results"\n to: "perform_code_review_and_publish"\n - from: "perform_code_review_and_publish"\n to: "end"\n\nflow:\n entry_point: "build_review_context"\n',
|
|
2629
|
+
"convert_to_gl_ci": `name: "Convert to GitLab CI"
|
|
2630
|
+
description: |
|
|
2631
|
+
Converts Jenkins pipelines (Jenkinsfile) to GitLab CI/CD configuration files (.gitlab-ci.yml).
|
|
2632
|
+
This flow reads a Jenkins configuration file, translates it to GitLab CI format with validation,
|
|
2633
|
+
and creates a merge request with the converted configuration.
|
|
2634
|
+
product_group: agent_foundations
|
|
2635
|
+
version: "v1"
|
|
2636
|
+
environment: ambient
|
|
2637
|
+
|
|
2638
|
+
components:
|
|
2639
|
+
- name: "load_jenkins_file"
|
|
2640
|
+
type: DeterministicStepComponent
|
|
2641
|
+
inputs:
|
|
2642
|
+
- from: "context:goal"
|
|
2643
|
+
as: "file_path"
|
|
2644
|
+
tool_name: "read_file"
|
|
2645
|
+
ui_log_events:
|
|
2646
|
+
- "on_tool_execution_success"
|
|
2647
|
+
- "on_tool_execution_failed"
|
|
2648
|
+
|
|
2649
|
+
- name: "convert_to_gitlab_ci"
|
|
2650
|
+
type: AgentComponent
|
|
2651
|
+
prompt_id: "convert_to_gl_ci"
|
|
2652
|
+
prompt_version: "^1.0.0"
|
|
2653
|
+
inputs:
|
|
2654
|
+
- from: "context:load_jenkins_file.tool_responses"
|
|
2655
|
+
as: "jenkins_file_content"
|
|
2656
|
+
toolset:
|
|
2657
|
+
- "create_file_with_contents"
|
|
2658
|
+
- "read_file"
|
|
2659
|
+
ui_log_events:
|
|
2660
|
+
- "on_agent_final_answer"
|
|
2661
|
+
- "on_tool_execution_success"
|
|
2662
|
+
- "on_tool_execution_failed"
|
|
2663
|
+
|
|
2664
|
+
- name: "create_repository_branch"
|
|
2665
|
+
type: AgentComponent
|
|
2666
|
+
prompt_id: "create_repository_branch"
|
|
2667
|
+
prompt_version: "^1.0.0"
|
|
2668
|
+
toolset:
|
|
2669
|
+
- "run_git_command"
|
|
2670
|
+
inputs:
|
|
2671
|
+
- from: "context:project_id"
|
|
2672
|
+
as: "project_id"
|
|
2673
|
+
- from: "context:inputs.agent_platform_standard_context.primary_branch"
|
|
2674
|
+
as: "ref"
|
|
2675
|
+
- from: "context:project_http_url_to_repo"
|
|
2676
|
+
as: "repository_url"
|
|
2677
|
+
- from: "Duo Agent: Convert to GitLab CI"
|
|
2678
|
+
as: "naming_context"
|
|
2679
|
+
literal: True
|
|
2680
|
+
- from: "context:workflow_id"
|
|
2681
|
+
as: "workflow_id"
|
|
2682
|
+
- from: "context:inputs.user_rule"
|
|
2683
|
+
as: "agents_dot_md"
|
|
2684
|
+
optional: True
|
|
2685
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2686
|
+
as: "workspace_agent_skills"
|
|
2687
|
+
optional: True
|
|
2688
|
+
ui_log_events:
|
|
2689
|
+
- "on_tool_execution_success"
|
|
2690
|
+
- "on_tool_execution_failed"
|
|
2691
|
+
|
|
2692
|
+
- name: "git_commit"
|
|
2693
|
+
type: AgentComponent
|
|
2694
|
+
prompt_id: "commit_changes"
|
|
2695
|
+
prompt_version: "^1.0.0"
|
|
2696
|
+
inputs:
|
|
2697
|
+
- from: "context:project_http_url_to_repo"
|
|
2698
|
+
as: "repository_url"
|
|
2699
|
+
- from: "context:inputs.user_rule"
|
|
2700
|
+
as: "agents_dot_md"
|
|
2701
|
+
optional: True
|
|
2702
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2703
|
+
as: "workspace_agent_skills"
|
|
2704
|
+
optional: True
|
|
2705
|
+
toolset:
|
|
2706
|
+
- "run_git_command"
|
|
2707
|
+
ui_log_events:
|
|
2708
|
+
- "on_tool_execution_success"
|
|
2709
|
+
- "on_tool_execution_failed"
|
|
2710
|
+
|
|
2711
|
+
- name: "git_push"
|
|
2712
|
+
type: AgentComponent
|
|
2713
|
+
prompt_id: "convert_ci_push_changes"
|
|
2714
|
+
prompt_version: "^1.0.0"
|
|
2715
|
+
inputs:
|
|
2716
|
+
- from: "context:project_http_url_to_repo"
|
|
2717
|
+
as: "repository_url"
|
|
2718
|
+
- from: "context:create_repository_branch.final_answer"
|
|
2719
|
+
as: "target_branch_details"
|
|
2720
|
+
- from: "context:inputs.agent_platform_standard_context.primary_branch"
|
|
2721
|
+
as: "source_branch"
|
|
2722
|
+
- from: "context:project_id"
|
|
2723
|
+
as: "project_id"
|
|
2724
|
+
- from: "context:workflow_id"
|
|
2725
|
+
as: "workflow_id"
|
|
2726
|
+
- from: "context:session_url"
|
|
2727
|
+
as: "session_url"
|
|
2728
|
+
- from: "context:inputs.user_rule"
|
|
2729
|
+
as: "agents_dot_md"
|
|
2730
|
+
optional: True
|
|
2731
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2732
|
+
as: "workspace_agent_skills"
|
|
2733
|
+
optional: True
|
|
2734
|
+
toolset:
|
|
2735
|
+
- "run_git_command"
|
|
2736
|
+
- "create_merge_request"
|
|
2737
|
+
ui_log_events:
|
|
2738
|
+
- "on_agent_final_answer"
|
|
2739
|
+
- "on_tool_execution_success"
|
|
2740
|
+
- "on_tool_execution_failed"
|
|
2741
|
+
|
|
2742
|
+
routers:
|
|
2743
|
+
- from: "load_jenkins_file"
|
|
2744
|
+
to: "convert_to_gitlab_ci"
|
|
2745
|
+
- from: "convert_to_gitlab_ci"
|
|
2746
|
+
to: "create_repository_branch"
|
|
2747
|
+
- from: "create_repository_branch"
|
|
2748
|
+
to: "git_commit"
|
|
2749
|
+
- from: "git_commit"
|
|
2750
|
+
to: "git_push"
|
|
2751
|
+
- from: "git_push"
|
|
2752
|
+
to: "end"
|
|
2753
|
+
|
|
2754
|
+
flow:
|
|
2755
|
+
entry_point: "load_jenkins_file"
|
|
2756
|
+
inputs:
|
|
2757
|
+
- category: agent_user_environment
|
|
2758
|
+
input_schema:
|
|
2759
|
+
source_branch:
|
|
2760
|
+
type: string
|
|
2761
|
+
description: Source branch of the Jenkins file
|
|
2762
|
+
- category: agent_platform_standard_context
|
|
2763
|
+
input_schema:
|
|
2764
|
+
workload_branch:
|
|
2765
|
+
type: string
|
|
2766
|
+
description: The workload branch for the GitLab Merge Request
|
|
2767
|
+
primary_branch:
|
|
2768
|
+
type: string
|
|
2769
|
+
description: Merge Request target branch
|
|
2770
|
+
session_owner_id:
|
|
2771
|
+
type: string
|
|
2772
|
+
description: Human user's ID that initiated the flow
|
|
2773
|
+
`,
|
|
2774
|
+
"developer": `version: "v1"
|
|
2775
|
+
environment: ambient
|
|
2776
|
+
|
|
2777
|
+
components:
|
|
2778
|
+
- name: "issue_parser"
|
|
2779
|
+
type: DeterministicStepComponent
|
|
2780
|
+
tool_name: "get_issue"
|
|
2781
|
+
inputs:
|
|
2782
|
+
- from: "context:goal"
|
|
2783
|
+
as: "url"
|
|
2784
|
+
ui_log_events:
|
|
2785
|
+
- "on_tool_execution_success"
|
|
2786
|
+
- "on_tool_execution_failed"
|
|
2787
|
+
- name: "create_repository_branch"
|
|
2788
|
+
type: AgentComponent
|
|
2789
|
+
prompt_id: "create_repository_branch"
|
|
2790
|
+
prompt_version: "^1.0.0"
|
|
2791
|
+
toolset:
|
|
2792
|
+
- "run_git_command"
|
|
2793
|
+
model_size_preference: "small"
|
|
2794
|
+
inputs:
|
|
2795
|
+
- from: "context:project_id"
|
|
2796
|
+
as: "project_id"
|
|
2797
|
+
- from: "context:inputs.agent_platform_standard_context.primary_branch"
|
|
2798
|
+
as: "ref"
|
|
2799
|
+
- from: "context:project_http_url_to_repo"
|
|
2800
|
+
as: "repository_url"
|
|
2801
|
+
- from: "context:issue_parser.tool_responses"
|
|
2802
|
+
as: "naming_context"
|
|
2803
|
+
- from: "context:workflow_id"
|
|
2804
|
+
as: "workflow_id"
|
|
2805
|
+
- from: "context:inputs.user_rule"
|
|
2806
|
+
as: "agents_dot_md"
|
|
2807
|
+
optional: True
|
|
2808
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2809
|
+
as: "workspace_agent_skills"
|
|
2810
|
+
optional: True
|
|
2811
|
+
ui_log_events:
|
|
2812
|
+
- "on_tool_execution_success"
|
|
2813
|
+
- "on_tool_execution_failed"
|
|
2814
|
+
- name: "draft_merge_request_creator"
|
|
2815
|
+
type: OneOffComponent
|
|
2816
|
+
prompt_id: "create_merge_request"
|
|
2817
|
+
prompt_version: "^1.0.0"
|
|
2818
|
+
tool_name: "create_merge_request"
|
|
2819
|
+
model_size_preference: "small"
|
|
2820
|
+
inputs:
|
|
2821
|
+
- from: "context:goal"
|
|
2822
|
+
as: "issue_url"
|
|
2823
|
+
- from: "context:project_id"
|
|
2824
|
+
as: "project_id"
|
|
2825
|
+
- from: "context:issue_parser.tool_responses"
|
|
2826
|
+
as: "issue_details"
|
|
2827
|
+
- from: "context:create_repository_branch.final_answer"
|
|
2828
|
+
as: "source_branch_details"
|
|
2829
|
+
- from: "context:inputs.agent_platform_standard_context.primary_branch"
|
|
2830
|
+
as: "target_branch"
|
|
2831
|
+
- from: "context:inputs.user_rule"
|
|
2832
|
+
as: "agents_dot_md"
|
|
2833
|
+
optional: True
|
|
2834
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2835
|
+
as: "workspace_agent_skills"
|
|
2836
|
+
optional: True
|
|
2837
|
+
ui_log_events:
|
|
2838
|
+
- "on_tool_call_input"
|
|
2839
|
+
- "on_tool_execution_success"
|
|
2840
|
+
- "on_tool_execution_failed"
|
|
2841
|
+
- name: "tool_listing_agent"
|
|
2842
|
+
type: AgentComponent
|
|
2843
|
+
prompt_id: "list_available_tools"
|
|
2844
|
+
prompt_version: "^1.0.0"
|
|
2845
|
+
model_size_preference: "large"
|
|
2846
|
+
toolset:
|
|
2847
|
+
- "list_issues"
|
|
2848
|
+
- "get_issue"
|
|
2849
|
+
- "list_issue_notes"
|
|
2850
|
+
- "get_issue_note"
|
|
2851
|
+
- "get_job_logs"
|
|
2852
|
+
- "get_merge_request"
|
|
2853
|
+
- "get_pipeline_failing_jobs"
|
|
2854
|
+
- "get_project"
|
|
2855
|
+
- "list_all_merge_request_notes"
|
|
2856
|
+
- "list_merge_request_diffs"
|
|
2857
|
+
- "gitlab_issue_search"
|
|
2858
|
+
- "gitlab_merge_request_search"
|
|
2859
|
+
- "read_file"
|
|
2860
|
+
- "create_file_with_contents"
|
|
2861
|
+
- "edit_file"
|
|
2862
|
+
- "find_files"
|
|
2863
|
+
- "grep"
|
|
2864
|
+
- "mkdir"
|
|
2865
|
+
- "get_epic"
|
|
2866
|
+
- "list_epics"
|
|
2867
|
+
- "get_repository_file"
|
|
2868
|
+
- "list_dir"
|
|
2869
|
+
- "list_epic_notes"
|
|
2870
|
+
- "get_epic_note"
|
|
2871
|
+
- "get_commit"
|
|
2872
|
+
- "run_command"
|
|
2873
|
+
- name: "planning_agent"
|
|
2874
|
+
type: AgentComponent
|
|
2875
|
+
prompt_id: "developer_planner"
|
|
2876
|
+
prompt_version: "^1.0.0"
|
|
2877
|
+
model_size_preference: "small"
|
|
2878
|
+
inputs:
|
|
2879
|
+
- from: "context:project_id"
|
|
2880
|
+
as: "project_id"
|
|
2881
|
+
- from: "context:project_http_url_to_repo"
|
|
2882
|
+
as: "repository_url"
|
|
2883
|
+
- from: "context:issue_parser.tool_responses"
|
|
2884
|
+
as: "issue_details"
|
|
2885
|
+
- from: "context:tool_listing_agent.final_answer"
|
|
2886
|
+
as: "coding_agent_tools"
|
|
2887
|
+
- from: "context:inputs.user_rule"
|
|
2888
|
+
as: "agents_dot_md"
|
|
2889
|
+
optional: True
|
|
2890
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2891
|
+
as: "workspace_agent_skills"
|
|
2892
|
+
optional: True
|
|
2893
|
+
toolset:
|
|
2894
|
+
- "list_issues"
|
|
2895
|
+
- "get_issue"
|
|
2896
|
+
- "list_issue_notes"
|
|
2897
|
+
- "get_issue_note"
|
|
2898
|
+
- "get_job_logs"
|
|
2899
|
+
- "get_merge_request"
|
|
2900
|
+
- "get_project"
|
|
2901
|
+
- "get_pipeline_failing_jobs"
|
|
2902
|
+
- "list_all_merge_request_notes"
|
|
2903
|
+
- "list_merge_request_diffs"
|
|
2904
|
+
- "gitlab_issue_search"
|
|
2905
|
+
- "gitlab_merge_request_search"
|
|
2906
|
+
- "read_file"
|
|
2907
|
+
- "find_files"
|
|
2908
|
+
- "list_dir"
|
|
2909
|
+
- "grep"
|
|
2910
|
+
- "get_epic"
|
|
2911
|
+
- "list_epics"
|
|
2912
|
+
- "get_repository_file"
|
|
2913
|
+
- "list_epic_notes"
|
|
2914
|
+
- "get_epic_note"
|
|
2915
|
+
- "get_commit"
|
|
2916
|
+
- "list_commits"
|
|
2917
|
+
- "get_commit_comments"
|
|
2918
|
+
- "get_commit_diff"
|
|
2919
|
+
- "get_work_item"
|
|
2920
|
+
- "list_work_items"
|
|
2921
|
+
- "get_work_item_notes"
|
|
2922
|
+
ui_log_events:
|
|
2923
|
+
- "on_tool_execution_success"
|
|
2924
|
+
- "on_tool_execution_failed"
|
|
2925
|
+
- "on_agent_final_answer"
|
|
2926
|
+
|
|
2927
|
+
- name: "add_plan_comment"
|
|
2928
|
+
type: OneOffComponent
|
|
2929
|
+
prompt_id: "developer_add_plan_comment"
|
|
2930
|
+
prompt_version: "^1.0.0"
|
|
2931
|
+
model_size_preference: "small"
|
|
2932
|
+
inputs:
|
|
2933
|
+
- from: "context:planning_agent.final_answer"
|
|
2934
|
+
as: "plan_text"
|
|
2935
|
+
- from: "context:draft_merge_request_creator.tool_responses"
|
|
2936
|
+
as: "merge_request_details"
|
|
2937
|
+
- from: "context:inputs.user_rule"
|
|
2938
|
+
as: "agents_dot_md"
|
|
2939
|
+
optional: True
|
|
2940
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2941
|
+
as: "workspace_agent_skills"
|
|
2942
|
+
optional: True
|
|
2943
|
+
toolset:
|
|
2944
|
+
- "create_merge_request_note"
|
|
2945
|
+
ui_log_events:
|
|
2946
|
+
- "on_tool_execution_success"
|
|
2947
|
+
- "on_tool_execution_failed"
|
|
2948
|
+
|
|
2949
|
+
- name: "programming_agent"
|
|
2950
|
+
type: AgentComponent
|
|
2951
|
+
prompt_id: "programming_agent"
|
|
2952
|
+
prompt_version: "^1.0.0"
|
|
2953
|
+
model_size_preference: "small"
|
|
2954
|
+
inputs:
|
|
2955
|
+
- from: "context:project_id"
|
|
2956
|
+
as: "project_id"
|
|
2957
|
+
- from: "context:project_http_url_to_repo"
|
|
2958
|
+
as: "repository_url"
|
|
2959
|
+
- from: "context:planning_agent.final_answer"
|
|
2960
|
+
as: "planner_handover"
|
|
2961
|
+
- from: "context:inputs.os_information"
|
|
2962
|
+
as: "os_information"
|
|
2963
|
+
- from: "context:inputs.user_rule"
|
|
2964
|
+
as: "agents_dot_md"
|
|
2965
|
+
optional: True
|
|
2966
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2967
|
+
as: "workspace_agent_skills"
|
|
2968
|
+
optional: True
|
|
2969
|
+
toolset:
|
|
2970
|
+
- "list_issues"
|
|
2971
|
+
- "get_issue"
|
|
2972
|
+
- "list_issue_notes"
|
|
2973
|
+
- "get_issue_note"
|
|
2974
|
+
- "get_job_logs"
|
|
2975
|
+
- "get_merge_request"
|
|
2976
|
+
- "get_pipeline_failing_jobs"
|
|
2977
|
+
- "get_project"
|
|
2978
|
+
- "list_all_merge_request_notes"
|
|
2979
|
+
- "list_merge_request_diffs"
|
|
2980
|
+
- "gitlab_issue_search"
|
|
2981
|
+
- "gitlab_merge_request_search"
|
|
2982
|
+
- "read_file"
|
|
2983
|
+
- "create_file_with_contents"
|
|
2984
|
+
- "edit_file"
|
|
2985
|
+
- "find_files"
|
|
2986
|
+
- "grep"
|
|
2987
|
+
- "mkdir"
|
|
2988
|
+
- "get_epic"
|
|
2989
|
+
- "list_epics"
|
|
2990
|
+
- "get_repository_file"
|
|
2991
|
+
- "list_dir"
|
|
2992
|
+
- "list_epic_notes"
|
|
2993
|
+
- "get_epic_note"
|
|
2994
|
+
- "get_commit"
|
|
2995
|
+
- "run_command"
|
|
2996
|
+
ui_log_events:
|
|
2997
|
+
- "on_tool_execution_success"
|
|
2998
|
+
- "on_tool_execution_failed"
|
|
2999
|
+
- "on_agent_final_answer"
|
|
3000
|
+
- name: "git_actions"
|
|
3001
|
+
type: OneOffComponent
|
|
3002
|
+
prompt_id: "push_to_remote"
|
|
3003
|
+
prompt_version: "^1.0.0"
|
|
3004
|
+
model_size_preference: "small"
|
|
3005
|
+
inputs:
|
|
3006
|
+
- from: "context:project_http_url_to_repo"
|
|
3007
|
+
as: "repository_url"
|
|
3008
|
+
- from: "context:programming_agent.final_answer"
|
|
3009
|
+
as: "programming_agent_handover"
|
|
3010
|
+
- from: "context:draft_merge_request_creator.tool_responses"
|
|
3011
|
+
as: "merge_request_details"
|
|
3012
|
+
- from: "context:inputs.agent_platform_standard_context.session_owner_id"
|
|
3013
|
+
as: "assignee_id"
|
|
3014
|
+
- from: "context:goal"
|
|
3015
|
+
as: "issue_url"
|
|
3016
|
+
- from: "context:inputs.user_rule"
|
|
3017
|
+
as: "agents_dot_md"
|
|
3018
|
+
optional: True
|
|
3019
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3020
|
+
as: "workspace_agent_skills"
|
|
3021
|
+
optional: True
|
|
3022
|
+
toolset:
|
|
3023
|
+
- "run_git_command"
|
|
3024
|
+
- "update_merge_request"
|
|
3025
|
+
ui_log_events:
|
|
3026
|
+
- "on_tool_execution_success"
|
|
3027
|
+
- "on_tool_execution_failed"
|
|
3028
|
+
|
|
3029
|
+
routers:
|
|
3030
|
+
- from: "issue_parser"
|
|
3031
|
+
to: "create_repository_branch"
|
|
3032
|
+
- from: "create_repository_branch"
|
|
3033
|
+
to: "draft_merge_request_creator"
|
|
3034
|
+
- from: "draft_merge_request_creator"
|
|
3035
|
+
to: "tool_listing_agent"
|
|
3036
|
+
- from: "tool_listing_agent"
|
|
3037
|
+
to: "planning_agent"
|
|
3038
|
+
- from: "planning_agent"
|
|
3039
|
+
to: "add_plan_comment"
|
|
3040
|
+
- from: "add_plan_comment"
|
|
3041
|
+
to: "programming_agent"
|
|
3042
|
+
- from: "programming_agent"
|
|
3043
|
+
to: "git_actions"
|
|
3044
|
+
- from: "git_actions"
|
|
3045
|
+
condition:
|
|
3046
|
+
input: "context:git_actions.execution_result"
|
|
3047
|
+
routes:
|
|
3048
|
+
"success": "end"
|
|
3049
|
+
"failed": "abort"
|
|
3050
|
+
|
|
3051
|
+
flow:
|
|
3052
|
+
entry_point: "issue_parser"
|
|
3053
|
+
inputs:
|
|
3054
|
+
- category: agent_platform_standard_context
|
|
3055
|
+
input_schema:
|
|
3056
|
+
workload_branch:
|
|
3057
|
+
type: string
|
|
3058
|
+
description: The workload branch for the GitLab Merge Request
|
|
3059
|
+
primary_branch:
|
|
3060
|
+
type: string
|
|
3061
|
+
description: Merge Request target branch
|
|
3062
|
+
session_owner_id:
|
|
3063
|
+
type: string
|
|
3064
|
+
description: Human user's ID that initiated the flow
|
|
3065
|
+
`,
|
|
3066
|
+
"duo_permissions_assistant": `version: "v1"
|
|
3067
|
+
environment: chat-partial
|
|
3068
|
+
components:
|
|
3069
|
+
- name: "duo_permissions_assistant"
|
|
3070
|
+
type: AgentComponent
|
|
3071
|
+
prompt_id: "duo_permissions_assistant_prompt"
|
|
3072
|
+
inputs:
|
|
3073
|
+
- from: "context:goal"
|
|
3074
|
+
as: "goal"
|
|
3075
|
+
toolset:
|
|
3076
|
+
- "gitlab_graphql"
|
|
3077
|
+
- "update_form_permissions"
|
|
3078
|
+
ui_log_events:
|
|
3079
|
+
- "on_agent_final_answer"
|
|
3080
|
+
- "on_tool_execution_success"
|
|
3081
|
+
routers: []
|
|
3082
|
+
flow:
|
|
3083
|
+
entry_point: "duo_permissions_assistant"
|
|
3084
|
+
prompts:
|
|
3085
|
+
- name: "duo_permissions_assistant_prompt"
|
|
3086
|
+
prompt_id: "duo_permissions_assistant_prompt"
|
|
3087
|
+
model:
|
|
3088
|
+
params:
|
|
3089
|
+
max_tokens: 8192
|
|
3090
|
+
unit_primitives:
|
|
3091
|
+
- duo_agent_platform
|
|
3092
|
+
prompt_template:
|
|
3093
|
+
system: |
|
|
3094
|
+
You are a GitLab fine-grained access token permissions assistant.
|
|
3095
|
+
You help users pick the right permissions for their access tokens.
|
|
3096
|
+
|
|
3097
|
+
STEP 1 \u2014 FETCH PERMISSIONS:
|
|
3098
|
+
On the first message, call the gitlab_graphql tool with this query to get all available permissions:
|
|
3099
|
+
query GetAccessTokenPermissions { accessTokenPermissions { name description } }
|
|
3100
|
+
|
|
3101
|
+
STEP 2 \u2014 SUGGEST PERMISSIONS:
|
|
3102
|
+
Using ONLY permission names returned by the tool:
|
|
3103
|
+
- Always suggest specific permissions immediately based on what the user describes. Do not ask clarifying questions about scope, projects, groups, or access level \u2014 you cannot act on that information.
|
|
3104
|
+
- Apply the principle of least privilege.
|
|
3105
|
+
- Do not suggest legacy scopes (api, read_api, write_repository, etc.) or anything not in the tool results.
|
|
3106
|
+
- If nothing matches, say: "No matching permissions are available."
|
|
3107
|
+
- IMPORTANT: Each user message is a standalone request. Do NOT use prior conversation turns to infer what is currently selected \u2014 the user may have changed the form manually between messages. Only act on what the user explicitly asks for right now.
|
|
3108
|
+
- If the user's request is vague, make your best guess and explain your reasoning. The user can refine afterward.
|
|
3109
|
+
|
|
3110
|
+
STEP 3 \u2014 APPLY CHANGES:
|
|
3111
|
+
After explaining your reasoning, call the update_form_permissions tool to apply the changes.
|
|
3112
|
+
Only include "select" and/or "clear" as needed.
|
|
3113
|
+
user: |
|
|
3114
|
+
{{goal}}
|
|
3115
|
+
placeholder: history
|
|
3116
|
+
`,
|
|
3117
|
+
"fix_pipeline": `name: "Fix pipeline"
|
|
3118
|
+
description: |
|
|
3119
|
+
Fix pipeline flow can be run for a failed CI jobs.
|
|
3120
|
+
It opens a new MR with changes that address root cause of CI job failures.
|
|
3121
|
+
product_group: agent_foundations
|
|
3122
|
+
version: "v1"
|
|
3123
|
+
environment: ambient
|
|
3124
|
+
|
|
3125
|
+
components:
|
|
3126
|
+
- name: "fix_pipeline_context"
|
|
3127
|
+
type: AgentComponent
|
|
3128
|
+
prompt_id: "fix_pipeline_context"
|
|
3129
|
+
prompt_version: "^1.0.0"
|
|
3130
|
+
inputs:
|
|
3131
|
+
- from: "context:goal"
|
|
3132
|
+
as: "pipeline_url"
|
|
3133
|
+
- from: "context:inputs.merge_request.url"
|
|
3134
|
+
as: "merge_request_url"
|
|
3135
|
+
- from: "context:inputs.user_rule"
|
|
3136
|
+
as: "agents_dot_md"
|
|
3137
|
+
optional: True
|
|
3138
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3139
|
+
as: "workspace_agent_skills"
|
|
3140
|
+
optional: True
|
|
3141
|
+
toolset:
|
|
3142
|
+
- get_downstream_pipelines
|
|
3143
|
+
- get_pipeline_failing_jobs
|
|
3144
|
+
- get_job_logs
|
|
3145
|
+
- list_merge_request_diffs
|
|
3146
|
+
- read_file
|
|
3147
|
+
- find_files
|
|
3148
|
+
- list_dir
|
|
3149
|
+
ui_log_events:
|
|
3150
|
+
- "on_agent_final_answer"
|
|
3151
|
+
- "on_tool_execution_success"
|
|
3152
|
+
- "on_tool_execution_failed"
|
|
3153
|
+
|
|
3154
|
+
- name: "fix_pipeline_decide_approach"
|
|
3155
|
+
type: AgentComponent
|
|
3156
|
+
prompt_id: "fix_pipeline_decide_approach"
|
|
3157
|
+
prompt_version: "^1.0.0"
|
|
3158
|
+
response_schema_id: "fix_pipeline_decide_approach"
|
|
3159
|
+
response_schema_version: "^1.0.0"
|
|
3160
|
+
inputs:
|
|
3161
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3162
|
+
as: "fix_pipeline_context_results"
|
|
3163
|
+
toolset: []
|
|
3164
|
+
ui_log_events:
|
|
3165
|
+
- "on_agent_final_answer"
|
|
3166
|
+
|
|
3167
|
+
- name: "fix_pipeline_add_comment"
|
|
3168
|
+
type: AgentComponent
|
|
3169
|
+
prompt_id: "fix_pipeline_add_comment"
|
|
3170
|
+
prompt_version: "^1.0.0"
|
|
3171
|
+
inputs:
|
|
3172
|
+
- from: "context:goal"
|
|
3173
|
+
as: "pipeline_url"
|
|
3174
|
+
- from: "context:inputs.merge_request.url"
|
|
3175
|
+
as: "merge_request_url"
|
|
3176
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3177
|
+
as: "fix_pipeline_context_results"
|
|
3178
|
+
- from: "context:inputs.user_rule"
|
|
3179
|
+
as: "agents_dot_md"
|
|
3180
|
+
optional: True
|
|
3181
|
+
- from: "context:workflow_id"
|
|
3182
|
+
as: "workflow_id"
|
|
3183
|
+
- from: "context:session_url"
|
|
3184
|
+
as: "session_url"
|
|
3185
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3186
|
+
as: "workspace_agent_skills"
|
|
3187
|
+
optional: True
|
|
3188
|
+
toolset: ["create_merge_request_note"]
|
|
3189
|
+
ui_log_events:
|
|
3190
|
+
- "on_agent_final_answer"
|
|
3191
|
+
- "on_tool_execution_success"
|
|
3192
|
+
- "on_tool_execution_failed"
|
|
3193
|
+
|
|
3194
|
+
- name: "create_repository_branch"
|
|
3195
|
+
type: AgentComponent
|
|
3196
|
+
prompt_id: "create_repository_branch"
|
|
3197
|
+
prompt_version: "^1.0.0"
|
|
3198
|
+
toolset:
|
|
3199
|
+
- "run_git_command"
|
|
3200
|
+
inputs:
|
|
3201
|
+
- from: "context:project_id"
|
|
3202
|
+
as: "project_id"
|
|
3203
|
+
- from: "context:inputs.pipeline.source_branch"
|
|
3204
|
+
as: "ref"
|
|
3205
|
+
- from: "context:project_http_url_to_repo"
|
|
3206
|
+
as: "repository_url"
|
|
3207
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3208
|
+
as: "naming_context"
|
|
3209
|
+
- from: "context:workflow_id"
|
|
3210
|
+
as: "workflow_id"
|
|
3211
|
+
- from: "context:inputs.user_rule"
|
|
3212
|
+
as: "agents_dot_md"
|
|
3213
|
+
optional: True
|
|
3214
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3215
|
+
as: "workspace_agent_skills"
|
|
3216
|
+
optional: True
|
|
3217
|
+
ui_log_events:
|
|
3218
|
+
- "on_tool_execution_success"
|
|
3219
|
+
- "on_tool_execution_failed"
|
|
3220
|
+
|
|
3221
|
+
- name: "fix_pipeline_create_plan"
|
|
3222
|
+
type: AgentComponent
|
|
3223
|
+
prompt_id: "fix_pipeline_create_plan"
|
|
3224
|
+
prompt_version: "^1.0.0"
|
|
3225
|
+
inputs:
|
|
3226
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3227
|
+
as: "fix_pipeline_context_results"
|
|
3228
|
+
- from: "context:inputs.pipeline.source_branch"
|
|
3229
|
+
as: "source_branch"
|
|
3230
|
+
- from: "context:inputs.user_rule"
|
|
3231
|
+
as: "agents_dot_md"
|
|
3232
|
+
optional: True
|
|
3233
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3234
|
+
as: "workspace_agent_skills"
|
|
3235
|
+
optional: True
|
|
3236
|
+
toolset: []
|
|
3237
|
+
ui_log_events:
|
|
3238
|
+
- "on_agent_final_answer"
|
|
3239
|
+
|
|
3240
|
+
- name: "fix_pipeline_execution"
|
|
3241
|
+
type: AgentComponent
|
|
3242
|
+
prompt_id: "fix_pipeline_execution"
|
|
3243
|
+
prompt_version: "^1.0.0"
|
|
3244
|
+
inputs:
|
|
3245
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3246
|
+
as: "fix_pipeline_context_results"
|
|
3247
|
+
- from: "context:fix_pipeline_create_plan.final_answer"
|
|
3248
|
+
as: "fix_pipeline_plan"
|
|
3249
|
+
- from: "context:inputs.user_rule"
|
|
3250
|
+
as: "agents_dot_md"
|
|
3251
|
+
optional: True
|
|
3252
|
+
- from: "context:inputs.os_information"
|
|
3253
|
+
as: "os_information"
|
|
3254
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3255
|
+
as: "workspace_agent_skills"
|
|
3256
|
+
optional: True
|
|
3257
|
+
toolset:
|
|
3258
|
+
- create_file_with_contents
|
|
3259
|
+
- edit_file
|
|
3260
|
+
- find_files
|
|
3261
|
+
- mkdir
|
|
3262
|
+
- read_file
|
|
3263
|
+
- run_command
|
|
3264
|
+
ui_log_events:
|
|
3265
|
+
- "on_agent_final_answer"
|
|
3266
|
+
- "on_tool_execution_success"
|
|
3267
|
+
- "on_tool_execution_failed"
|
|
3268
|
+
|
|
3269
|
+
- name: "fix_pipeline_git_commit"
|
|
3270
|
+
type: AgentComponent
|
|
3271
|
+
prompt_id: "commit_changes"
|
|
3272
|
+
prompt_version: "^1.0.0"
|
|
3273
|
+
inputs:
|
|
3274
|
+
- from: "context:project_http_url_to_repo"
|
|
3275
|
+
as: "repository_url"
|
|
3276
|
+
- from: "context:inputs.user_rule"
|
|
3277
|
+
as: "agents_dot_md"
|
|
3278
|
+
optional: True
|
|
3279
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3280
|
+
as: "workspace_agent_skills"
|
|
3281
|
+
optional: True
|
|
3282
|
+
toolset:
|
|
3283
|
+
- "run_git_command"
|
|
3284
|
+
ui_log_events:
|
|
3285
|
+
- "on_tool_execution_success"
|
|
3286
|
+
- "on_tool_execution_failed"
|
|
3287
|
+
|
|
3288
|
+
- name: "fix_pipeline_git_push"
|
|
3289
|
+
type: AgentComponent
|
|
3290
|
+
prompt_id: "fix_pipeline_push_changes"
|
|
3291
|
+
prompt_version: "^1.0.0"
|
|
3292
|
+
inputs:
|
|
3293
|
+
- from: "context:goal"
|
|
3294
|
+
as: "pipeline_url"
|
|
3295
|
+
- from: "context:project_http_url_to_repo"
|
|
3296
|
+
as: "repository_url"
|
|
3297
|
+
- from: "context:inputs.merge_request.url"
|
|
3298
|
+
as: "merge_request_url"
|
|
3299
|
+
- from: "context:inputs.pipeline.source_branch"
|
|
3300
|
+
as: "source_branch"
|
|
3301
|
+
- from: "context:inputs.user_rule"
|
|
3302
|
+
as: "agents_dot_md"
|
|
3303
|
+
optional: True
|
|
3304
|
+
- from: "context:workflow_id"
|
|
3305
|
+
as: "workflow_id"
|
|
3306
|
+
- from: "context:session_url"
|
|
3307
|
+
as: "session_url"
|
|
3308
|
+
- from: "context:project_id"
|
|
3309
|
+
as: "project_id"
|
|
3310
|
+
- from: "context:create_repository_branch.final_answer"
|
|
3311
|
+
as: "target_branch_details"
|
|
3312
|
+
- from: "context:inputs.agent_platform_standard_context.primary_branch"
|
|
3313
|
+
as: "default_branch"
|
|
3314
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3315
|
+
as: "workspace_agent_skills"
|
|
3316
|
+
optional: True
|
|
3317
|
+
toolset:
|
|
3318
|
+
- "run_git_command"
|
|
3319
|
+
- "create_merge_request"
|
|
3320
|
+
ui_log_events:
|
|
3321
|
+
- "on_agent_final_answer"
|
|
3322
|
+
- "on_tool_execution_success"
|
|
3323
|
+
- "on_tool_execution_failed"
|
|
3324
|
+
|
|
3325
|
+
- name: "fix_pipeline_summarize"
|
|
3326
|
+
type: OneOffComponent
|
|
3327
|
+
prompt_id: "fix_pipeline_summarize_changes"
|
|
3328
|
+
prompt_version: "^1.0.0"
|
|
3329
|
+
inputs:
|
|
3330
|
+
- from: "context:goal"
|
|
3331
|
+
as: "pipeline_url"
|
|
3332
|
+
- from: "context:inputs.merge_request.url"
|
|
3333
|
+
as: "merge_request_url"
|
|
3334
|
+
- from: "context:inputs.user_rule"
|
|
3335
|
+
as: "agents_dot_md"
|
|
3336
|
+
optional: True
|
|
3337
|
+
- from: "context:fix_pipeline_git_push.final_answer"
|
|
3338
|
+
as: "git_push_response"
|
|
3339
|
+
- from: "context:workflow_id"
|
|
3340
|
+
as: "workflow_id"
|
|
3341
|
+
- from: "context:session_url"
|
|
3342
|
+
as: "session_url"
|
|
3343
|
+
- from: "context:fix_pipeline_execution.final_answer"
|
|
3344
|
+
as: "execution_results"
|
|
3345
|
+
- from: "context:inputs.agent_platform_standard_context.session_owner_id"
|
|
3346
|
+
as: "assignee_id"
|
|
3347
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3348
|
+
as: "workspace_agent_skills"
|
|
3349
|
+
optional: True
|
|
3350
|
+
toolset:
|
|
3351
|
+
- "update_merge_request"
|
|
3352
|
+
ui_log_events:
|
|
3353
|
+
- "on_agent_final_answer"
|
|
3354
|
+
- "on_tool_execution_success"
|
|
3355
|
+
- "on_tool_execution_failed"
|
|
3356
|
+
|
|
3357
|
+
- name: "fix_pipeline_decide_comment"
|
|
3358
|
+
type: AgentComponent
|
|
3359
|
+
prompt_id: "fix_pipeline_decide_comment"
|
|
3360
|
+
prompt_version: "^1.0.0"
|
|
3361
|
+
response_schema_id: "fix_pipeline_decide_comment"
|
|
3362
|
+
response_schema_version: "^1.0.0"
|
|
3363
|
+
inputs:
|
|
3364
|
+
- from: "context:inputs.merge_request.url"
|
|
3365
|
+
as: "merge_request_url"
|
|
3366
|
+
- from: "context:inputs.user_rule"
|
|
3367
|
+
as: "agents_dot_md"
|
|
3368
|
+
optional: True
|
|
3369
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3370
|
+
as: "workspace_agent_skills"
|
|
3371
|
+
optional: True
|
|
3372
|
+
toolset: []
|
|
3373
|
+
ui_log_events:
|
|
3374
|
+
- "on_agent_final_answer"
|
|
3375
|
+
|
|
3376
|
+
- name: "fix_pipeline_comment_link"
|
|
3377
|
+
type: OneOffComponent
|
|
3378
|
+
prompt_id: "fix_pipeline_comment_link"
|
|
3379
|
+
prompt_version: "^1.0.0"
|
|
3380
|
+
max_correction_attempts: 3
|
|
3381
|
+
inputs:
|
|
3382
|
+
- from: "context:inputs.merge_request.url"
|
|
3383
|
+
as: "merge_request_url"
|
|
3384
|
+
- from: "context:fix_pipeline_git_push.final_answer"
|
|
3385
|
+
as: "git_push_response"
|
|
3386
|
+
- from: "context:inputs.user_rule"
|
|
3387
|
+
as: "agents_dot_md"
|
|
3388
|
+
optional: True
|
|
3389
|
+
- from: "context:workflow_id"
|
|
3390
|
+
as: "workflow_id"
|
|
3391
|
+
- from: "context:session_url"
|
|
3392
|
+
as: "session_url"
|
|
3393
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3394
|
+
as: "workspace_agent_skills"
|
|
3395
|
+
optional: True
|
|
3396
|
+
toolset: ["create_merge_request_note"]
|
|
3397
|
+
ui_log_events:
|
|
3398
|
+
- "on_tool_execution_success"
|
|
3399
|
+
- "on_tool_execution_failed"
|
|
3400
|
+
|
|
3401
|
+
routers:
|
|
3402
|
+
- from: "fix_pipeline_context"
|
|
3403
|
+
to: "fix_pipeline_decide_approach"
|
|
3404
|
+
- from: "fix_pipeline_decide_approach"
|
|
3405
|
+
condition:
|
|
3406
|
+
input: "context:fix_pipeline_decide_approach.final_answer.decision"
|
|
3407
|
+
routes:
|
|
3408
|
+
"add_comment": "fix_pipeline_add_comment"
|
|
3409
|
+
"create_fix": "create_repository_branch"
|
|
3410
|
+
"default_route": "fix_pipeline_add_comment"
|
|
3411
|
+
- from: "fix_pipeline_add_comment"
|
|
3412
|
+
to: "end"
|
|
3413
|
+
- from: "create_repository_branch"
|
|
3414
|
+
to: "fix_pipeline_create_plan"
|
|
3415
|
+
- from: "fix_pipeline_create_plan"
|
|
3416
|
+
to: "fix_pipeline_execution"
|
|
3417
|
+
- from: "fix_pipeline_execution"
|
|
3418
|
+
to: "fix_pipeline_git_commit"
|
|
3419
|
+
- from: "fix_pipeline_git_commit"
|
|
3420
|
+
to: "fix_pipeline_git_push"
|
|
3421
|
+
- from: "fix_pipeline_git_push"
|
|
3422
|
+
to: "fix_pipeline_summarize"
|
|
3423
|
+
- from: "fix_pipeline_summarize"
|
|
3424
|
+
to: "fix_pipeline_decide_comment"
|
|
3425
|
+
- from: "fix_pipeline_decide_comment"
|
|
3426
|
+
condition:
|
|
3427
|
+
input: "context:fix_pipeline_decide_comment.final_answer.decision"
|
|
3428
|
+
routes:
|
|
3429
|
+
"comment_link": "fix_pipeline_comment_link"
|
|
3430
|
+
"end": "end"
|
|
3431
|
+
"default_route": "end"
|
|
3432
|
+
- from: "fix_pipeline_comment_link"
|
|
3433
|
+
to: "end"
|
|
3434
|
+
|
|
3435
|
+
flow:
|
|
3436
|
+
entry_point: "fix_pipeline_context"
|
|
3437
|
+
inputs:
|
|
3438
|
+
- category: merge_request
|
|
3439
|
+
input_schema:
|
|
3440
|
+
url:
|
|
3441
|
+
type: string
|
|
3442
|
+
format: uri
|
|
3443
|
+
description: The URL for the GitLab Merge Request
|
|
3444
|
+
- category: pipeline
|
|
3445
|
+
input_schema:
|
|
3446
|
+
source_branch:
|
|
3447
|
+
type: string
|
|
3448
|
+
- category: agent_platform_standard_context
|
|
3449
|
+
input_schema:
|
|
3450
|
+
workload_branch:
|
|
3451
|
+
type: string
|
|
3452
|
+
description: The workload branch for the GitLab Merge Request
|
|
3453
|
+
primary_branch:
|
|
3454
|
+
type: string
|
|
3455
|
+
description: Merge Request target branch
|
|
3456
|
+
session_owner_id:
|
|
3457
|
+
type: string
|
|
3458
|
+
description: Human user's ID that initiated the flow
|
|
3459
|
+
`,
|
|
3460
|
+
"fix_pipeline_next": `name: "Fix pipeline (next)"
|
|
3461
|
+
description: |
|
|
3462
|
+
Fix pipeline flow can be run for a failed CI jobs.
|
|
3463
|
+
When a merge request URL is provided, it pushes the fix directly to that MR's branch.
|
|
3464
|
+
Otherwise it opens a new MR with changes that address root cause of CI job failures.
|
|
3465
|
+
product_group: agent_foundations
|
|
3466
|
+
version: "v1"
|
|
3467
|
+
environment: ambient
|
|
3468
|
+
|
|
3469
|
+
components:
|
|
3470
|
+
- name: "fix_pipeline_context"
|
|
3471
|
+
type: AgentComponent
|
|
3472
|
+
prompt_id: "fix_pipeline_context"
|
|
3473
|
+
prompt_version: "^1.0.0"
|
|
3474
|
+
inputs:
|
|
3475
|
+
- from: "context:goal"
|
|
3476
|
+
as: "pipeline_url"
|
|
3477
|
+
- from: "context:inputs.merge_request.url"
|
|
3478
|
+
as: "merge_request_url"
|
|
3479
|
+
- from: "context:inputs.user_rule"
|
|
3480
|
+
as: "agents_dot_md"
|
|
3481
|
+
optional: True
|
|
3482
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3483
|
+
as: "workspace_agent_skills"
|
|
3484
|
+
optional: True
|
|
3485
|
+
toolset:
|
|
3486
|
+
- get_downstream_pipelines
|
|
3487
|
+
- get_pipeline_failing_jobs
|
|
3488
|
+
- get_job_logs
|
|
3489
|
+
- list_merge_request_diffs
|
|
3490
|
+
- read_file
|
|
3491
|
+
- find_files
|
|
3492
|
+
- list_dir
|
|
3493
|
+
ui_log_events:
|
|
3494
|
+
- "on_agent_final_answer"
|
|
3495
|
+
- "on_tool_execution_success"
|
|
3496
|
+
- "on_tool_execution_failed"
|
|
3497
|
+
|
|
3498
|
+
- name: "fix_pipeline_next_decide_approach"
|
|
3499
|
+
type: AgentComponent
|
|
3500
|
+
prompt_id: "fix_pipeline_next_decide_approach"
|
|
3501
|
+
prompt_version: "^1.0.0"
|
|
3502
|
+
response_schema_id: "fix_pipeline_next_decide_approach"
|
|
3503
|
+
response_schema_version: "^1.0.0"
|
|
3504
|
+
inputs:
|
|
3505
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3506
|
+
as: "fix_pipeline_context_results"
|
|
3507
|
+
- from: "context:inputs.merge_request.url"
|
|
3508
|
+
as: "merge_request_url"
|
|
3509
|
+
optional: True
|
|
3510
|
+
- from: "context:inputs.user_rule"
|
|
3511
|
+
as: "agents_dot_md"
|
|
3512
|
+
optional: True
|
|
3513
|
+
toolset: []
|
|
3514
|
+
ui_log_events:
|
|
3515
|
+
- "on_agent_final_answer"
|
|
3516
|
+
|
|
3517
|
+
- name: "fix_pipeline_add_comment"
|
|
3518
|
+
type: AgentComponent
|
|
3519
|
+
prompt_id: "fix_pipeline_add_comment"
|
|
3520
|
+
prompt_version: "^1.0.0"
|
|
3521
|
+
inputs:
|
|
3522
|
+
- from: "context:goal"
|
|
3523
|
+
as: "pipeline_url"
|
|
3524
|
+
- from: "context:inputs.merge_request.url"
|
|
3525
|
+
as: "merge_request_url"
|
|
3526
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3527
|
+
as: "fix_pipeline_context_results"
|
|
3528
|
+
- from: "context:inputs.user_rule"
|
|
3529
|
+
as: "agents_dot_md"
|
|
3530
|
+
optional: True
|
|
3531
|
+
- from: "context:workflow_id"
|
|
3532
|
+
as: "workflow_id"
|
|
3533
|
+
- from: "context:session_url"
|
|
3534
|
+
as: "session_url"
|
|
3535
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3536
|
+
as: "workspace_agent_skills"
|
|
3537
|
+
optional: True
|
|
3538
|
+
toolset: ["create_merge_request_note"]
|
|
3539
|
+
ui_log_events:
|
|
3540
|
+
- "on_agent_final_answer"
|
|
3541
|
+
- "on_tool_execution_success"
|
|
3542
|
+
- "on_tool_execution_failed"
|
|
3543
|
+
|
|
3544
|
+
- name: "fix_pipeline_next_checkout_existing_branch"
|
|
3545
|
+
type: AgentComponent
|
|
3546
|
+
prompt_id: "fix_pipeline_next_checkout_existing_branch"
|
|
3547
|
+
prompt_version: "^1.0.0"
|
|
3548
|
+
inputs:
|
|
3549
|
+
- from: "context:inputs.pipeline.source_branch"
|
|
3550
|
+
as: "ref"
|
|
3551
|
+
- from: "context:project_http_url_to_repo"
|
|
3552
|
+
as: "repository_url"
|
|
3553
|
+
- from: "context:inputs.user_rule"
|
|
3554
|
+
as: "agents_dot_md"
|
|
3555
|
+
optional: True
|
|
3556
|
+
toolset:
|
|
3557
|
+
- "run_git_command"
|
|
3558
|
+
ui_log_events:
|
|
3559
|
+
- "on_tool_execution_success"
|
|
3560
|
+
- "on_tool_execution_failed"
|
|
3561
|
+
|
|
3562
|
+
- name: "create_repository_branch"
|
|
3563
|
+
type: AgentComponent
|
|
3564
|
+
prompt_id: "create_repository_branch"
|
|
3565
|
+
prompt_version: "^1.0.0"
|
|
3566
|
+
toolset:
|
|
3567
|
+
- "run_git_command"
|
|
3568
|
+
inputs:
|
|
3569
|
+
- from: "context:project_id"
|
|
3570
|
+
as: "project_id"
|
|
3571
|
+
- from: "context:inputs.pipeline.source_branch"
|
|
3572
|
+
as: "ref"
|
|
3573
|
+
- from: "context:project_http_url_to_repo"
|
|
3574
|
+
as: "repository_url"
|
|
3575
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3576
|
+
as: "naming_context"
|
|
3577
|
+
- from: "context:workflow_id"
|
|
3578
|
+
as: "workflow_id"
|
|
3579
|
+
- from: "context:inputs.user_rule"
|
|
3580
|
+
as: "agents_dot_md"
|
|
3581
|
+
optional: True
|
|
3582
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3583
|
+
as: "workspace_agent_skills"
|
|
3584
|
+
optional: True
|
|
3585
|
+
ui_log_events:
|
|
3586
|
+
- "on_tool_execution_success"
|
|
3587
|
+
- "on_tool_execution_failed"
|
|
3588
|
+
|
|
3589
|
+
- name: "fix_pipeline_create_plan"
|
|
3590
|
+
type: AgentComponent
|
|
3591
|
+
prompt_id: "fix_pipeline_create_plan"
|
|
3592
|
+
prompt_version: "^1.0.0"
|
|
3593
|
+
inputs:
|
|
3594
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3595
|
+
as: "fix_pipeline_context_results"
|
|
3596
|
+
- from: "context:inputs.pipeline.source_branch"
|
|
3597
|
+
as: "source_branch"
|
|
3598
|
+
- from: "context:inputs.user_rule"
|
|
3599
|
+
as: "agents_dot_md"
|
|
3600
|
+
optional: True
|
|
3601
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3602
|
+
as: "workspace_agent_skills"
|
|
3603
|
+
optional: True
|
|
3604
|
+
toolset: []
|
|
3605
|
+
ui_log_events:
|
|
3606
|
+
- "on_agent_final_answer"
|
|
3607
|
+
|
|
3608
|
+
- name: "fix_pipeline_execution"
|
|
3609
|
+
type: AgentComponent
|
|
3610
|
+
prompt_id: "fix_pipeline_execution"
|
|
3611
|
+
prompt_version: "^1.0.0"
|
|
3612
|
+
inputs:
|
|
3613
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3614
|
+
as: "fix_pipeline_context_results"
|
|
3615
|
+
- from: "context:fix_pipeline_create_plan.final_answer"
|
|
3616
|
+
as: "fix_pipeline_plan"
|
|
3617
|
+
- from: "context:inputs.user_rule"
|
|
3618
|
+
as: "agents_dot_md"
|
|
3619
|
+
optional: True
|
|
3620
|
+
- from: "context:inputs.os_information"
|
|
3621
|
+
as: "os_information"
|
|
3622
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3623
|
+
as: "workspace_agent_skills"
|
|
3624
|
+
optional: True
|
|
3625
|
+
toolset:
|
|
3626
|
+
- create_file_with_contents
|
|
3627
|
+
- edit_file
|
|
3628
|
+
- find_files
|
|
3629
|
+
- mkdir
|
|
3630
|
+
- read_file
|
|
3631
|
+
- run_command
|
|
3632
|
+
ui_log_events:
|
|
3633
|
+
- "on_agent_final_answer"
|
|
3634
|
+
- "on_tool_execution_success"
|
|
3635
|
+
- "on_tool_execution_failed"
|
|
3636
|
+
|
|
3637
|
+
- name: "fix_pipeline_git_commit"
|
|
3638
|
+
type: AgentComponent
|
|
3639
|
+
prompt_id: "commit_changes"
|
|
3640
|
+
prompt_version: "^1.0.0"
|
|
3641
|
+
inputs:
|
|
3642
|
+
- from: "context:project_http_url_to_repo"
|
|
3643
|
+
as: "repository_url"
|
|
3644
|
+
- from: "context:inputs.user_rule"
|
|
3645
|
+
as: "agents_dot_md"
|
|
3646
|
+
optional: True
|
|
3647
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3648
|
+
as: "workspace_agent_skills"
|
|
3649
|
+
optional: True
|
|
3650
|
+
toolset:
|
|
3651
|
+
- "run_git_command"
|
|
3652
|
+
ui_log_events:
|
|
3653
|
+
- "on_tool_execution_success"
|
|
3654
|
+
- "on_tool_execution_failed"
|
|
3655
|
+
|
|
3656
|
+
- name: "fix_pipeline_git_push"
|
|
3657
|
+
type: AgentComponent
|
|
3658
|
+
prompt_id: "fix_pipeline_next_push_changes"
|
|
3659
|
+
prompt_version: "^1.0.0"
|
|
3660
|
+
inputs:
|
|
3661
|
+
- from: "context:project_http_url_to_repo"
|
|
3662
|
+
as: "repository_url"
|
|
3663
|
+
- from: "context:inputs.user_rule"
|
|
3664
|
+
as: "agents_dot_md"
|
|
3665
|
+
optional: True
|
|
3666
|
+
toolset:
|
|
3667
|
+
- "run_git_command"
|
|
3668
|
+
ui_log_events:
|
|
3669
|
+
- "on_agent_final_answer"
|
|
3670
|
+
- "on_tool_execution_success"
|
|
3671
|
+
- "on_tool_execution_failed"
|
|
3672
|
+
|
|
3673
|
+
- name: "fix_pipeline_next_create_new_mr"
|
|
3674
|
+
type: AgentComponent
|
|
3675
|
+
prompt_id: "fix_pipeline_next_create_new_mr"
|
|
3676
|
+
prompt_version: "^1.0.0"
|
|
3677
|
+
inputs:
|
|
3678
|
+
- from: "context:goal"
|
|
3679
|
+
as: "pipeline_url"
|
|
3680
|
+
- from: "context:project_id"
|
|
3681
|
+
as: "project_id"
|
|
3682
|
+
- from: "context:inputs.pipeline.source_branch"
|
|
3683
|
+
as: "source_branch"
|
|
3684
|
+
- from: "context:inputs.agent_platform_standard_context.primary_branch"
|
|
3685
|
+
as: "default_branch"
|
|
3686
|
+
- from: "context:fix_pipeline_git_push.final_answer"
|
|
3687
|
+
as: "git_push_response"
|
|
3688
|
+
- from: "context:fix_pipeline_execution.final_answer"
|
|
3689
|
+
as: "execution_results"
|
|
3690
|
+
- from: "context:workflow_id"
|
|
3691
|
+
as: "workflow_id"
|
|
3692
|
+
- from: "context:session_url"
|
|
3693
|
+
as: "session_url"
|
|
3694
|
+
- from: "context:inputs.agent_platform_standard_context.session_owner_id"
|
|
3695
|
+
as: "assignee_id"
|
|
3696
|
+
- from: "context:inputs.user_rule"
|
|
3697
|
+
as: "agents_dot_md"
|
|
3698
|
+
optional: True
|
|
3699
|
+
toolset:
|
|
3700
|
+
- "create_merge_request"
|
|
3701
|
+
- "update_merge_request"
|
|
3702
|
+
ui_log_events:
|
|
3703
|
+
- "on_agent_final_answer"
|
|
3704
|
+
- "on_tool_execution_success"
|
|
3705
|
+
- "on_tool_execution_failed"
|
|
3706
|
+
|
|
3707
|
+
- name: "fix_pipeline_next_comment_existing_mr"
|
|
3708
|
+
type: OneOffComponent
|
|
3709
|
+
prompt_id: "fix_pipeline_next_comment_existing_mr"
|
|
3710
|
+
prompt_version: "^1.0.0"
|
|
3711
|
+
inputs:
|
|
3712
|
+
- from: "context:inputs.merge_request.url"
|
|
3713
|
+
as: "merge_request_url"
|
|
3714
|
+
- from: "context:fix_pipeline_git_push.final_answer"
|
|
3715
|
+
as: "git_push_response"
|
|
3716
|
+
- from: "context:fix_pipeline_execution.final_answer"
|
|
3717
|
+
as: "execution_results"
|
|
3718
|
+
- from: "context:workflow_id"
|
|
3719
|
+
as: "workflow_id"
|
|
3720
|
+
- from: "context:session_url"
|
|
3721
|
+
as: "session_url"
|
|
3722
|
+
- from: "context:inputs.user_rule"
|
|
3723
|
+
as: "agents_dot_md"
|
|
3724
|
+
optional: True
|
|
3725
|
+
toolset:
|
|
3726
|
+
- "create_merge_request_note"
|
|
3727
|
+
ui_log_events:
|
|
3728
|
+
- "on_tool_execution_success"
|
|
3729
|
+
- "on_tool_execution_failed"
|
|
3730
|
+
|
|
3731
|
+
routers:
|
|
3732
|
+
- from: "fix_pipeline_context"
|
|
3733
|
+
to: "fix_pipeline_next_decide_approach"
|
|
3734
|
+
- from: "fix_pipeline_next_decide_approach"
|
|
3735
|
+
condition:
|
|
3736
|
+
input: "context:fix_pipeline_next_decide_approach.final_answer.decision"
|
|
3737
|
+
routes:
|
|
3738
|
+
"add_comment": "fix_pipeline_add_comment"
|
|
3739
|
+
"create_fix_on_new_mr": "create_repository_branch"
|
|
3740
|
+
"create_fix_on_existing_mr": "fix_pipeline_next_checkout_existing_branch"
|
|
3741
|
+
"default_route": "fix_pipeline_add_comment"
|
|
3742
|
+
- from: "fix_pipeline_add_comment"
|
|
3743
|
+
to: "end"
|
|
3744
|
+
- from: "fix_pipeline_next_checkout_existing_branch"
|
|
3745
|
+
to: "fix_pipeline_create_plan"
|
|
3746
|
+
- from: "create_repository_branch"
|
|
3747
|
+
to: "fix_pipeline_create_plan"
|
|
3748
|
+
- from: "fix_pipeline_create_plan"
|
|
3749
|
+
to: "fix_pipeline_execution"
|
|
3750
|
+
- from: "fix_pipeline_execution"
|
|
3751
|
+
to: "fix_pipeline_git_commit"
|
|
3752
|
+
- from: "fix_pipeline_git_commit"
|
|
3753
|
+
to: "fix_pipeline_git_push"
|
|
3754
|
+
- from: "fix_pipeline_git_push"
|
|
3755
|
+
condition:
|
|
3756
|
+
input: "context:fix_pipeline_next_decide_approach.final_answer.decision"
|
|
3757
|
+
routes:
|
|
3758
|
+
"create_fix_on_existing_mr": "fix_pipeline_next_comment_existing_mr"
|
|
3759
|
+
"create_fix_on_new_mr": "fix_pipeline_next_create_new_mr"
|
|
3760
|
+
"default_route": "fix_pipeline_next_create_new_mr"
|
|
3761
|
+
- from: "fix_pipeline_next_comment_existing_mr"
|
|
3762
|
+
to: "end"
|
|
3763
|
+
- from: "fix_pipeline_next_create_new_mr"
|
|
3764
|
+
to: "end"
|
|
3765
|
+
|
|
3766
|
+
flow:
|
|
3767
|
+
entry_point: "fix_pipeline_context"
|
|
3768
|
+
inputs:
|
|
3769
|
+
- category: merge_request
|
|
3770
|
+
input_schema:
|
|
3771
|
+
url:
|
|
3772
|
+
type: string
|
|
3773
|
+
format: uri
|
|
3774
|
+
description: The URL for the GitLab Merge Request
|
|
3775
|
+
- category: pipeline
|
|
3776
|
+
input_schema:
|
|
3777
|
+
source_branch:
|
|
3778
|
+
type: string
|
|
3779
|
+
- category: agent_platform_standard_context
|
|
3780
|
+
input_schema:
|
|
3781
|
+
workload_branch:
|
|
3782
|
+
type: string
|
|
3783
|
+
description: The workload branch for the GitLab Merge Request
|
|
3784
|
+
primary_branch:
|
|
3785
|
+
type: string
|
|
3786
|
+
description: Merge Request target branch
|
|
3787
|
+
session_owner_id:
|
|
3788
|
+
type: string
|
|
3789
|
+
description: Human user's ID that initiated the flow
|
|
3790
|
+
`,
|
|
3791
|
+
"project_activity": 'name: "Project Activity"\ndescription: |\n Summarize the issue and MR activity in a project for a given timeframe.\nproduct_group: agent_foundations\nversion: "v1"\nenvironment: ambient\n\ncomponents:\n - name: "fetch_new_issues"\n type: AgentComponent\n prompt_id: "project_activity_fetch_issues_new"\n prompt_version: "^1.0.0"\n toolset:\n - "list_issues"\n - "gitlab_api_get"\n - "gitlab_issue_search"\n - "get_issue"\n - "list_issue_notes"\n - "get_issue_note"\n inputs:\n - from: "context:project_id"\n as: "project_id"\n - from: "context:goal"\n as: "date_range"\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - "on_agent_final_answer"\n - name: "fetch_closed_issues"\n type: AgentComponent\n prompt_id: "project_activity_fetch_issues_closed"\n prompt_version: "^1.0.0"\n toolset:\n - "list_issues"\n - "gitlab_api_get"\n - "gitlab_issue_search"\n - "get_issue"\n - "list_issue_notes"\n - "get_issue_note"\n inputs:\n - from: "context:project_id"\n as: "project_id"\n - from: "context:goal"\n as: "date_range"\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - "on_agent_final_answer"\n - name: "fetch_updated_issues"\n type: AgentComponent\n prompt_id: "project_activity_fetch_issues_updated"\n prompt_version: "^1.0.0"\n toolset:\n - "list_issues"\n - "gitlab_api_get"\n - "gitlab_issue_search"\n - "get_issue"\n - "list_issue_notes"\n - "get_issue_note"\n inputs:\n - from: "context:project_id"\n as: "project_id"\n - from: "context:goal"\n as: "date_range"\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - "on_agent_final_answer"\n - name: "fetch_new_merge_requests"\n type: AgentComponent\n prompt_id: "project_activity_fetch_merge_requests_new"\n prompt_version: "^1.0.0"\n toolset:\n - "gitlab_merge_request_search"\n - "gitlab_api_get"\n - "get_merge_request"\n - "list_all_merge_request_notes"\n - "list_merge_request_diffs"\n inputs:\n - from: "context:project_id"\n as: "project_id"\n - from: "context:goal"\n as: "date_range"\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - "on_agent_final_answer"\n - name: "fetch_closed_merge_requests"\n type: AgentComponent\n prompt_id: "project_activity_fetch_merge_requests_closed"\n prompt_version: "^1.0.0"\n toolset:\n - "gitlab_merge_request_search"\n - "gitlab_api_get"\n - "get_merge_request"\n - "list_all_merge_request_notes"\n - "list_merge_request_diffs"\n inputs:\n - from: "context:project_id"\n as: "project_id"\n - from: "context:goal"\n as: "date_range"\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - "on_agent_final_answer"\n - name: "fetch_updated_merge_requests"\n type: AgentComponent\n prompt_id: "project_activity_fetch_merge_requests_updated"\n prompt_version: "^1.0.0"\n toolset:\n - "gitlab_merge_request_search"\n - "gitlab_api_get"\n - "get_merge_request"\n - "list_all_merge_request_notes"\n - "list_merge_request_diffs"\n inputs:\n - from: "context:project_id"\n as: "project_id"\n - from: "context:goal"\n as: "date_range"\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - "on_agent_final_answer"\n - name: "summarize_activity"\n type: AgentComponent\n prompt_id: "project_activity_summarize_project_activity"\n prompt_version: "^1.0.0"\n toolset: []\n inputs:\n - from: "context:project_id"\n as: "project_id"\n - from: "context:goal"\n as: "date_range"\n - from: "context:fetch_new_issues.final_answer"\n as: "new_issues_data"\n - from: "context:fetch_closed_issues.final_answer"\n as: "closed_issues_data"\n - from: "context:fetch_updated_issues.final_answer"\n as: "updated_issues_data"\n - from: "context:fetch_new_merge_requests.final_answer"\n as: "new_merge_requests_data"\n - from: "context:fetch_closed_merge_requests.final_answer"\n as: "closed_merge_requests_data"\n - from: "context:fetch_updated_merge_requests.final_answer"\n as: "updated_merge_requests_data"\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - "on_agent_final_answer"\n - name: "create_summary_issue"\n type: AgentComponent\n prompt_id: "project_activity_create_summary_issue"\n prompt_version: "^1.0.0"\n toolset:\n - "create_issue"\n - "gitlab_api_post"\n inputs:\n - from: "context:project_id"\n as: "project_id"\n - from: "context:goal"\n as: "date_range"\n - from: "context:summarize_activity.final_answer"\n as: "summary"\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - "on_agent_final_answer"\n\nrouters:\n - from: "fetch_new_issues"\n to: "fetch_closed_issues"\n - from: "fetch_closed_issues"\n to: "fetch_updated_issues"\n - from: "fetch_updated_issues"\n to: "fetch_new_merge_requests"\n - from: "fetch_new_merge_requests"\n to: "fetch_closed_merge_requests"\n - from: "fetch_closed_merge_requests"\n to: "fetch_updated_merge_requests"\n - from: "fetch_updated_merge_requests"\n to: "summarize_activity"\n - from: "summarize_activity"\n to: "create_summary_issue"\n - from: "create_summary_issue"\n to: "end"\n\nflow:\n entry_point: "fetch_new_issues"\n inputs: []\n',
|
|
3792
|
+
"resolve_sast_vulnerability": 'version: "v1"\nenvironment: ambient\n\ncomponents:\n - name: "gather_context"\n type: DeterministicStepComponent\n tool_name: "get_vulnerability_details"\n inputs:\n - { from: "context:goal", as: "vulnerability_id" }\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n\n - name: "evaluate_vuln_fp_status"\n type: DeterministicStepComponent\n tool_name: "evaluate_vuln_fp_status"\n inputs:\n - from: "context:gather_context.tool_responses"\n as: "vulnerability_json"\n toolset:\n - "evaluate_vuln_fp_status"\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n\n - name: "ensure_clean_git_state"\n type: AgentComponent\n prompt_id: "ensure_clean_git_state"\n prompt_version: "1.0.0"\n response_schema_id: "ensure_clean_git_state"\n response_schema_version: "^1.0.0"\n inputs:\n - from: "context:project_http_url_to_repo"\n as: "repository_url"\n - from: "context:project_default_branch"\n as: "default_branch"\n toolset:\n - "run_git_command"\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n\n - name: "create_repository_branch"\n type: AgentComponent\n prompt_id: "create_repository_branch"\n prompt_version: "^1.0.0"\n toolset:\n - "run_git_command"\n inputs:\n - from: "context:project_id"\n as: "project_id"\n - from: "context:project_default_branch"\n as: "ref"\n - from: "context:project_http_url_to_repo"\n as: "repository_url"\n - from: "context:gather_context.tool_responses"\n as: "naming_context"\n - from: "context:workflow_id"\n as: "workflow_id"\n - from: "context:inputs.user_rule"\n as: "agents_dot_md"\n optional: True\n - from: "context:inputs.workspace_agent_skills"\n as: "workspace_agent_skills"\n optional: True\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n\n - name: "execute_fix"\n type: AgentComponent\n prompt_id: "resolve_sast_vulnerability_execution"\n prompt_version: "^1.0.0"\n inputs:\n - from: "context:gather_context.tool_responses"\n as: "vulnerability_context"\n toolset:\n - "edit_file"\n - "create_file_with_contents"\n - "read_file"\n - "read_files"\n - "grep"\n - "find_files"\n - "list_dir"\n ui_log_events:\n - "on_agent_final_answer"\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n\n - name: "validate_fix_has_changes"\n type: AgentComponent\n prompt_id: "validate_sast_fix_has_changes"\n prompt_version: "1.0.0"\n response_schema_id: "validate_sast_fix_has_changes"\n response_schema_version: "^1.0.0"\n inputs:\n - from: "context:execute_fix.final_answer"\n as: "fix_execution_result"\n - from: "context:project_http_url_to_repo"\n as: "repository_url"\n toolset:\n - "run_git_command"\n ui_log_events:\n - "on_agent_final_answer"\n\n - name: "commit_changes"\n type: OneOffComponent\n prompt_id: "resolve_sast_vulnerability_commit"\n prompt_version: "^1.0.0"\n max_correction_attempts: 3\n inputs:\n - from: "context:gather_context.tool_responses"\n as: "vulnerability_context"\n - from: "context:project_http_url_to_repo"\n as: "repository_url"\n - from: "context:workflow_id"\n as: "workflow_id"\n toolset:\n - "run_git_command"\n ui_log_events:\n - "on_tool_call_input"\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n\n - name: "push_and_create_mr"\n type: OneOffComponent\n prompt_id: "resolve_sast_vulnerability_push_and_create_mr"\n prompt_version: "^1.0.0"\n max_correction_attempts: 3\n inputs:\n - from: "context:gather_context.tool_responses"\n as: "vulnerability_details"\n - from: "context:execute_fix.final_answer"\n as: "fix_summary"\n - from: "context:project_http_url_to_repo"\n as: "repository_url"\n - from: "context:workflow_id"\n as: "workflow_id"\n - from: "context:project_id"\n as: "project_id"\n - from: "context:project_default_branch"\n as: "default_branch"\n - from: "context:create_repository_branch.final_answer"\n as: "source_branch_name"\n toolset:\n - "run_git_command"\n - "create_merge_request"\n ui_log_events:\n - "on_tool_call_input"\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n\n - name: "link_vulnerability"\n type: DeterministicStepComponent\n tool_name: "link_vulnerability_to_merge_request"\n inputs:\n - from: "context:goal"\n as: "vulnerability_id"\n - from: "context:push_and_create_mr.parsed_responses.created_merge_request.id"\n as: "merge_request_id"\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n\n - name: "evaluate_merge_request"\n type: AgentComponent\n prompt_id: "resolve_sast_evaluate_mr_readiness"\n prompt_version: "^1.0.0"\n inputs:\n - from: "context:gather_context.tool_responses"\n as: "vulnerability_context"\n - from: "context:execute_fix.final_answer"\n as: "fix_execution_result"\n - from: "context:push_and_create_mr.tool_responses"\n as: "git_operations_result"\n - from: "context:project_http_url_to_repo"\n as: "repository_url"\n - from: "context:workflow_id"\n as: "workflow_id"\n toolset:\n - "get_merge_request"\n - "list_merge_request_diffs"\n ui_log_events:\n - "on_agent_final_answer"\n - "on_tool_execution_success"\n\nrouters:\n - from: "gather_context"\n to: "evaluate_vuln_fp_status"\n - from: "evaluate_vuln_fp_status"\n condition:\n input: "context:evaluate_vuln_fp_status.tool_responses"\n routes:\n "skip_false_positive": "end"\n "proceed_with_fix": "ensure_clean_git_state"\n default_route: "ensure_clean_git_state"\n - from: "ensure_clean_git_state"\n to: "create_repository_branch"\n - from: "create_repository_branch"\n to: "execute_fix"\n - from: "execute_fix"\n to: "validate_fix_has_changes"\n - from: "validate_fix_has_changes"\n condition:\n input: "context:validate_fix_has_changes.final_answer.decision"\n routes:\n "proceed": "commit_changes"\n "no_changes": "end"\n default_route: "end"\n - from: "commit_changes"\n to: "push_and_create_mr"\n - from: "push_and_create_mr"\n to: "evaluate_merge_request"\n - from: "evaluate_merge_request"\n to: "link_vulnerability"\n - from: "link_vulnerability"\n to: "end"\n\nflow:\n entry_point: "gather_context"\n',
|
|
3793
|
+
"sast_fp_detection": 'version: v1\nenvironment: ambient\ncomponents:\n - name: "sast_vulnerability_details_component"\n type: DeterministicStepComponent\n inputs:\n - from: "context:goal"\n as: vulnerability_id\n tool_name: "get_vulnerability_details"\n max_correction_attempts: 3\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - name: "validate_sast_vulnerability_component"\n type: AgentComponent\n prompt_id: "validate_sast_vulnerability_agent_prompt"\n prompt_version: "1.0.0"\n response_schema_id: "validate_sast_vulnerability"\n response_schema_version: "^1.0.0"\n inputs:\n - from: "context:sast_vulnerability_details_component.tool_responses"\n as: vulnerability_details_json\n toolset: []\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - "on_agent_final_answer"\n - name: "sast_vulnerability_source_file_component"\n type: OneOffComponent\n prompt_id: "sast_vulnerability_source_file_agent_prompt"\n prompt_version: "1.0.0"\n inputs:\n - from: "context:sast_vulnerability_details_component.tool_responses"\n as: vulnerability_details_json\n toolset:\n - "get_repository_file"\n max_correction_attempts: 3\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - name: "sast_vulnerability_lines_component"\n type: OneOffComponent\n prompt_id: "sast_vulnerability_lines_agent_prompt"\n prompt_version: "1.0.0"\n inputs:\n - from: "context:sast_vulnerability_details_component.tool_responses"\n as: vulnerability_details_json\n - from: "context:sast_vulnerability_source_file_component.tool_responses"\n as: vulnerability_source_code\n toolset:\n - "extract_lines_from_text"\n max_correction_attempts: 3\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - name: "sast_vulnerability_report_component"\n type: AgentComponent\n prompt_id: "sast_vulnerability_report_agent_prompt"\n prompt_version: "1.0.0"\n inputs:\n - from: "context:sast_vulnerability_details_component.tool_responses"\n as: vulnerability_details_json\n - from: "context:sast_vulnerability_lines_component.tool_responses"\n as: vulnerable_lines\n - from: "context:sast_vulnerability_source_file_component.tool_responses"\n as: vulnerability_source_code\n toolset: []\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - name: "sast_fp_detection_agent"\n type: AgentComponent\n prompt_id: "sast_fp_detection_agent_prompt"\n prompt_version: "^1.0.0"\n inputs:\n - from: "context:sast_vulnerability_report_component.final_answer"\n as: vulnerability_details\n toolset:\n - "read_file"\n - "get_repository_file"\n - "list_repository_tree"\n - "find_files"\n - "gitlab_blob_search"\n ui_log_events:\n - "on_agent_final_answer"\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - name: "sast_post_results_to_gitlab_component"\n type: OneOffComponent\n prompt_id: "sast_post_results_to_gitlab_agent_prompt"\n prompt_version: "1.0.0"\n inputs:\n - from: "context:sast_vulnerability_details_component.tool_responses"\n as: vulnerability_details_json\n - from: "context:sast_fp_detection_agent.final_answer"\n as: sast_fp_detection_analysis\n toolset:\n - "post_sast_fp_analysis_to_gitlab"\n max_correction_attempts: 3\n ui_log_events:\n - "on_agent_final_answer"\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\nrouters:\n - from: "sast_vulnerability_details_component"\n to: "validate_sast_vulnerability_component"\n - from: "validate_sast_vulnerability_component"\n condition:\n input: "context:validate_sast_vulnerability_component.final_answer.decision"\n routes:\n "valid": "sast_vulnerability_source_file_component"\n "invalid": "end"\n - from: "sast_vulnerability_source_file_component"\n to: "sast_vulnerability_lines_component"\n - from: "sast_vulnerability_lines_component"\n to: "sast_vulnerability_report_component"\n - from: "sast_vulnerability_report_component"\n to: "sast_fp_detection_agent"\n - from: "sast_fp_detection_agent"\n to: "sast_post_results_to_gitlab_component"\n - from: "sast_post_results_to_gitlab_component"\n to: "end"\nflow:\n entry_point: "sast_vulnerability_details_component"\n',
|
|
3794
|
+
"secrets_fp_detection": 'version: "v1"\nenvironment: ambient\ncomponents:\n - name: "secret_vulnerability_details_component"\n type: DeterministicStepComponent\n inputs:\n - from: "context:goal"\n as: vulnerability_id\n tool_name: "get_vulnerability_details"\n max_correction_attempts: 3\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - name: "secret_vulnerability_source_file_component"\n type: OneOffComponent\n prompt_id: "secret_vulnerability_source_file_agent_prompt"\n prompt_version: "1.0.0"\n inputs:\n - from: "context:secret_vulnerability_details_component.tool_responses"\n as: vulnerability_details_json\n toolset:\n - "get_repository_file"\n max_correction_attempts: 3\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - name: "secret_vulnerability_lines_component"\n type: OneOffComponent\n prompt_id: "secret_vulnerability_lines_agent_prompt"\n prompt_version: "1.0.0"\n inputs:\n - from: "context:secret_vulnerability_details_component.tool_responses"\n as: vulnerability_details_json\n - from: "context:secret_vulnerability_source_file_component.tool_responses"\n as: vulnerability_source_code\n toolset:\n - "extract_lines_from_text"\n max_correction_attempts: 3\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - name: "secret_vulnerability_report_component"\n type: AgentComponent\n prompt_id: "secret_vulnerability_report_agent_prompt"\n prompt_version: "1.0.0"\n inputs:\n - from: "context:secret_vulnerability_details_component.tool_responses"\n as: vulnerability_details_json\n - from: "context:secret_vulnerability_lines_component.tool_responses"\n as: vulnerable_lines\n - from: "context:secret_vulnerability_source_file_component.tool_responses"\n as: vulnerability_source_code\n toolset: []\n ui_log_events:\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - name: "secret_fp_detection_agent"\n type: AgentComponent\n prompt_id: "secret_fp_detection_agent_prompt"\n prompt_version: "^1.0.0"\n inputs:\n - from: "context:secret_vulnerability_report_component.final_answer"\n as: vulnerability_details\n toolset:\n - "read_file"\n - "get_repository_file"\n - "list_repository_tree"\n - "find_files"\n - "gitlab_blob_search"\n ui_log_events:\n - "on_agent_final_answer"\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\n - name: "secret_post_results_to_gitlab_component"\n type: OneOffComponent\n prompt_id: "secret_post_results_to_gitlab_agent_prompt"\n prompt_version: "1.0.0"\n inputs:\n - from: "context:secret_vulnerability_details_component.tool_responses"\n as: vulnerability_details_json\n - from: "context:secret_fp_detection_agent.final_answer"\n as: secret_fp_detection_analysis\n toolset:\n - "post_secret_fp_analysis_to_gitlab"\n max_correction_attempts: 3\n ui_log_events:\n - "on_agent_final_answer"\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\nrouters:\n - from: "secret_vulnerability_details_component"\n to: "secret_vulnerability_source_file_component"\n - from: "secret_vulnerability_source_file_component"\n to: "secret_vulnerability_lines_component"\n - from: "secret_vulnerability_lines_component"\n to: "secret_vulnerability_report_component"\n - from: "secret_vulnerability_report_component"\n to: "secret_fp_detection_agent"\n - from: "secret_fp_detection_agent"\n to: "secret_post_results_to_gitlab_component"\n - from: "secret_post_results_to_gitlab_component"\n to: "end"\nflow:\n entry_point: "secret_vulnerability_details_component"\n',
|
|
3795
|
+
"slack_assistant": 'version: "v1"\nenvironment: chat\ncomponents:\n - name: "slack_assistant"\n type: AgentComponent\n prompt_id: "slack_assistant_prompt"\n inputs:\n - from: "context:goal"\n as: "goal"\n toolset:\n - "gitlab_api_get"\n - "gitlab_graphql"\n - "gitlab_issue_search"\n - "get_issue"\n - "get_merge_request"\n - "get_wiki_page"\n - "get_repository_file"\n - "gitlab_documentation_search"\n ui_log_events:\n - "on_agent_final_answer"\n - "on_tool_execution_success"\n - "on_tool_execution_failed"\nrouters:\n - from: "slack_assistant"\n to: "end"\nflow:\n entry_point: "slack_assistant"\nprompts:\n - name: "slack_assistant_prompt"\n prompt_id: "slack_assistant_prompt"\n unit_primitives:\n - duo_agent_platform\n prompt_template:\n system: |\n You are a helpful GitLab assistant that people talk to in Slack.\n\n You receive the Slack thread as context (in <slack-thread-context> tags) and a user_context with the invoking user\'s identity and their group memberships. Use the groups to broaden your searches \u2014 search across multiple groups, not just the default namespace. When scoped searches return no results, always fall back to instance-wide search using `GET /api/v4/search?scope=issues&search=...` or `GET /api/v4/projects?search=...&search_namespaces=true`.\n\n Keep Slack responses short. Use bullet points and link to GitLab so people can click through. Format your responses using Slack mrkdwn syntax:\n - *bold* for emphasis\n - _italic_ for secondary emphasis\n - `code` for inline code or commands\n - ```multi-line code``` for code blocks\n - <url|link text> for URLs (e.g., <https://gitlab.com/my-issue|View issue>)\n - <#channel_id> to link channels\n - <@user_id> to mention users\n - <!subteam^group_id> to mention groups\n - @here, @channel, @everyone for special mentions\n - ~strikethrough~ to strikethrough text\n - > quote for block quotes\n - - item for bullet points\n - \\n for line breaks\n\n You are currently *read-only* and act on behalf of the person who mentioned you. You can search and retrieve GitLab data (issues, projects, merge requests, users, etc.) but you cannot create, update, or delete anything. If someone asks you to create an issue or make changes, let them know this capability is not available yet and suggest they do it in GitLab directly.\n\n When a specific tool exists for the task (e.g. `get_issue`, `get_merge_request`, `get_wiki_page`, `get_repository_file`), prefer it over `gitlab_api_get` \u2014 it handles URL parsing and compound operations automatically. Use `gitlab_api_get` and `gitlab_graphql` as fallbacks for resources not covered by a specific tool.\n\n The GitLab API accepts URL-encoded project paths (like `gitlab-org%2Fgitlab`) wherever it accepts numeric project IDs. Use this when people reference projects by path.\n user: |\n {{goal}}\n placeholder: history\n'
|
|
3796
|
+
};
|
|
3797
|
+
|
|
2624
3798
|
// src/catalog.ts
|
|
3799
|
+
function extractFlowInputs(flowConfig) {
|
|
3800
|
+
if (!flowConfig) return [];
|
|
3801
|
+
const flow = flowConfig.flow;
|
|
3802
|
+
if (!flow?.inputs) return [];
|
|
3803
|
+
const inputs = flow.inputs;
|
|
3804
|
+
return inputs.map((inp) => ({
|
|
3805
|
+
category: inp.category,
|
|
3806
|
+
fields: Object.fromEntries(
|
|
3807
|
+
Object.entries(inp.input_schema ?? {}).map(([k, v]) => [
|
|
3808
|
+
k,
|
|
3809
|
+
{ type: v.type, description: v.description, format: v.format }
|
|
3810
|
+
])
|
|
3811
|
+
)
|
|
3812
|
+
}));
|
|
3813
|
+
}
|
|
2625
3814
|
var FOUNDATIONAL_QUERY = `
|
|
2626
3815
|
query AiFoundationalChatAgents($projectId: ProjectID!, $after: String) {
|
|
2627
3816
|
aiFoundationalChatAgents(projectId: $projectId, first: 50, after: $after) {
|
|
@@ -2634,12 +3823,14 @@ query AiCatalogCustomAgents($projectId: ProjectID!, $after: String) {
|
|
|
2634
3823
|
aiCatalogConfiguredItems(first: 50, projectId: $projectId, after: $after) {
|
|
2635
3824
|
pageInfo { hasNextPage endCursor }
|
|
2636
3825
|
nodes {
|
|
3826
|
+
id
|
|
2637
3827
|
enabled
|
|
2638
3828
|
item {
|
|
2639
3829
|
id
|
|
2640
3830
|
name
|
|
2641
3831
|
description
|
|
2642
3832
|
foundational
|
|
3833
|
+
foundationalFlowReference
|
|
2643
3834
|
itemType
|
|
2644
3835
|
latestVersion { id }
|
|
2645
3836
|
}
|
|
@@ -2650,6 +3841,16 @@ var FLOW_CONFIG_QUERY = `
|
|
|
2650
3841
|
query AiCatalogAgentFlowConfig($versionId: AiCatalogItemVersionID!) {
|
|
2651
3842
|
aiCatalogAgentFlowConfig(agentVersionId: $versionId, flowConfigType: CHAT)
|
|
2652
3843
|
}`;
|
|
3844
|
+
var FLOW_VERSION_DEFINITION_QUERY = `
|
|
3845
|
+
query FlowVersionDefinition($itemId: AiCatalogItemID!) {
|
|
3846
|
+
aiCatalogItem(id: $itemId) {
|
|
3847
|
+
latestVersion {
|
|
3848
|
+
... on AiCatalogFlowVersion {
|
|
3849
|
+
definition
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
}`;
|
|
2653
3854
|
async function gql(instanceUrl, token, query, variables) {
|
|
2654
3855
|
const res = await fetch(`${instanceUrl.replace(/\/$/, "")}/api/graphql`, {
|
|
2655
3856
|
method: "POST",
|
|
@@ -2700,27 +3901,43 @@ async function fetchCustomAgents(instanceUrl, token, projectId) {
|
|
|
2700
3901
|
if (!node.enabled) continue;
|
|
2701
3902
|
const item = node.item;
|
|
2702
3903
|
if (!item) continue;
|
|
2703
|
-
if (
|
|
3904
|
+
if (!item.latestVersion?.id) continue;
|
|
3905
|
+
const validTypes = ["AGENT", "FLOW"];
|
|
3906
|
+
if (!validTypes.includes(item.itemType)) continue;
|
|
3907
|
+
if (item.foundational && item.itemType === "AGENT") continue;
|
|
2704
3908
|
if (seen.has(item.id)) continue;
|
|
2705
3909
|
seen.add(item.id);
|
|
3910
|
+
const consumerNumericId = node.id ? parseInt(String(node.id).split("/").pop() ?? "", 10) || void 0 : void 0;
|
|
2706
3911
|
try {
|
|
2707
3912
|
const cfgData = await gql(instanceUrl, token, FLOW_CONFIG_QUERY, {
|
|
2708
3913
|
versionId: item.latestVersion.id
|
|
2709
3914
|
});
|
|
2710
3915
|
const yamlStr = cfgData?.aiCatalogAgentFlowConfig;
|
|
2711
|
-
|
|
2712
|
-
const parsed = load(yamlStr);
|
|
3916
|
+
const parsed = yamlStr ? load(yamlStr) : void 0;
|
|
2713
3917
|
agents.push({
|
|
2714
3918
|
identifier: item.id,
|
|
2715
3919
|
name: item.name,
|
|
2716
3920
|
description: item.description ?? "",
|
|
3921
|
+
itemType: item.itemType,
|
|
3922
|
+
workflowDefinition: item.foundationalFlowReference ?? void 0,
|
|
2717
3923
|
flowConfig: parsed,
|
|
2718
|
-
flowConfigSchemaVersion: parsed?.version ?? "v1",
|
|
2719
|
-
foundational:
|
|
2720
|
-
|
|
2721
|
-
|
|
3924
|
+
flowConfigSchemaVersion: parsed ? parsed?.version ?? "v1" : void 0,
|
|
3925
|
+
foundational: !!item.foundational,
|
|
3926
|
+
catalogItemVersionId: parseInt(item.latestVersion.id.split("/").pop() ?? "", 10) || void 0,
|
|
3927
|
+
consumerId: consumerNumericId,
|
|
3928
|
+
flowInputs: extractFlowInputs(parsed)
|
|
2722
3929
|
});
|
|
2723
3930
|
} catch {
|
|
3931
|
+
agents.push({
|
|
3932
|
+
identifier: item.id,
|
|
3933
|
+
name: item.name,
|
|
3934
|
+
description: item.description ?? "",
|
|
3935
|
+
itemType: item.itemType,
|
|
3936
|
+
workflowDefinition: item.foundationalFlowReference ?? void 0,
|
|
3937
|
+
foundational: !!item.foundational,
|
|
3938
|
+
catalogItemVersionId: parseInt(item.latestVersion.id.split("/").pop() ?? "", 10) || void 0,
|
|
3939
|
+
consumerId: consumerNumericId
|
|
3940
|
+
});
|
|
2724
3941
|
}
|
|
2725
3942
|
}
|
|
2726
3943
|
if (!page.pageInfo?.hasNextPage) break;
|
|
@@ -2728,6 +3945,145 @@ async function fetchCustomAgents(instanceUrl, token, projectId) {
|
|
|
2728
3945
|
}
|
|
2729
3946
|
return agents;
|
|
2730
3947
|
}
|
|
3948
|
+
async function getFlowDefinition(instanceUrl, token, opts) {
|
|
3949
|
+
const name = opts.flowName ?? `consumer-${opts.consumerId}`;
|
|
3950
|
+
let config = null;
|
|
3951
|
+
let apiError;
|
|
3952
|
+
if (opts.foundational && opts.workflowDefinition) {
|
|
3953
|
+
const flowKey = opts.workflowDefinition.split("/")[0];
|
|
3954
|
+
config = FOUNDATIONAL_FLOWS[flowKey] ?? null;
|
|
3955
|
+
}
|
|
3956
|
+
if (!config && !opts.foundational && opts.itemIdentifier) {
|
|
3957
|
+
try {
|
|
3958
|
+
const data = await gql(instanceUrl, token, FLOW_VERSION_DEFINITION_QUERY, {
|
|
3959
|
+
itemId: opts.itemIdentifier
|
|
3960
|
+
});
|
|
3961
|
+
config = data?.aiCatalogItem?.latestVersion?.definition ?? null;
|
|
3962
|
+
} catch (err) {
|
|
3963
|
+
apiError = err?.message ?? "Unknown error fetching flow definition";
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
if (!config && !opts.foundational && opts.catalogItemVersionId) {
|
|
3967
|
+
const versionGid = `gid://gitlab/Ai::Catalog::ItemVersion/${opts.catalogItemVersionId}`;
|
|
3968
|
+
try {
|
|
3969
|
+
const cfgData = await gql(instanceUrl, token, FLOW_CONFIG_QUERY, { versionId: versionGid });
|
|
3970
|
+
config = cfgData?.aiCatalogAgentFlowConfig ?? null;
|
|
3971
|
+
} catch (err) {
|
|
3972
|
+
if (!apiError) apiError = err?.message ?? "Unknown error fetching flow config";
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3975
|
+
return { config, name, ...apiError && !config ? { error: apiError } : {} };
|
|
3976
|
+
}
|
|
3977
|
+
var RESOLVE_ROOT_NAMESPACE_QUERY = `
|
|
3978
|
+
query resolveRootNamespace($projectPath: ID!) {
|
|
3979
|
+
project(fullPath: $projectPath) {
|
|
3980
|
+
id
|
|
3981
|
+
group {
|
|
3982
|
+
id
|
|
3983
|
+
rootNamespace { id }
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
}`;
|
|
3987
|
+
async function resolveRootNamespaceId(instanceUrl, token, projectPath) {
|
|
3988
|
+
try {
|
|
3989
|
+
const data = await gql(instanceUrl, token, RESOLVE_ROOT_NAMESPACE_QUERY, {
|
|
3990
|
+
projectPath
|
|
3991
|
+
});
|
|
3992
|
+
const rootGid = data?.project?.group?.rootNamespace?.id;
|
|
3993
|
+
if (rootGid) {
|
|
3994
|
+
return parseInt(rootGid.split("/").pop() ?? "", 10) || void 0;
|
|
3995
|
+
}
|
|
3996
|
+
} catch {
|
|
3997
|
+
}
|
|
3998
|
+
return void 0;
|
|
3999
|
+
}
|
|
4000
|
+
async function executeFlow(instanceUrl, token, projectPath, consumerId, goal, options) {
|
|
4001
|
+
const projectUrl = `${instanceUrl.replace(/\/$/, "")}/${projectPath}`;
|
|
4002
|
+
const additionalContext = [
|
|
4003
|
+
{
|
|
4004
|
+
Category: "agent_user_environment",
|
|
4005
|
+
Content: JSON.stringify({ project_url: projectUrl }),
|
|
4006
|
+
Metadata: "{}"
|
|
4007
|
+
}
|
|
4008
|
+
];
|
|
4009
|
+
if (options?.flowInputs) {
|
|
4010
|
+
for (const input of options.flowInputs) {
|
|
4011
|
+
additionalContext.push({
|
|
4012
|
+
Category: input.Category,
|
|
4013
|
+
Content: input.Content,
|
|
4014
|
+
Metadata: input.Metadata ?? "{}"
|
|
4015
|
+
});
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
const body = {
|
|
4019
|
+
project_id: projectPath,
|
|
4020
|
+
ai_catalog_item_consumer_id: consumerId,
|
|
4021
|
+
goal,
|
|
4022
|
+
start_workflow: true,
|
|
4023
|
+
environment: "ambient",
|
|
4024
|
+
agent_privileges: [1, 2, 3, 4, 5, 6],
|
|
4025
|
+
additional_context: additionalContext
|
|
4026
|
+
};
|
|
4027
|
+
if (options?.namespaceId) body.namespace_id = options.namespaceId;
|
|
4028
|
+
if (options?.issueId) body.issue_id = options.issueId;
|
|
4029
|
+
if (options?.mergeRequestId) body.merge_request_id = options.mergeRequestId;
|
|
4030
|
+
const rootNsId = await resolveRootNamespaceId(instanceUrl, token, projectPath);
|
|
4031
|
+
if (rootNsId) body.root_namespace_id = rootNsId;
|
|
4032
|
+
const res = await fetch(`${instanceUrl.replace(/\/$/, "")}/api/v4/ai/duo_workflows/workflows`, {
|
|
4033
|
+
method: "POST",
|
|
4034
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
4035
|
+
body: JSON.stringify(body)
|
|
4036
|
+
});
|
|
4037
|
+
if (!res.ok) {
|
|
4038
|
+
const text = await res.text();
|
|
4039
|
+
throw new Error(`Failed to execute flow (${res.status}): ${text}`);
|
|
4040
|
+
}
|
|
4041
|
+
return res.json();
|
|
4042
|
+
}
|
|
4043
|
+
var WORKFLOW_STATUS_QUERY = `
|
|
4044
|
+
query getWorkflowStatus($workflowId: AiDuoWorkflowsWorkflowID!) {
|
|
4045
|
+
duoWorkflowWorkflows(workflowId: $workflowId) {
|
|
4046
|
+
nodes {
|
|
4047
|
+
id
|
|
4048
|
+
status
|
|
4049
|
+
humanStatus
|
|
4050
|
+
createdAt
|
|
4051
|
+
updatedAt
|
|
4052
|
+
workflowDefinition
|
|
4053
|
+
lastExecutorLogsUrl
|
|
4054
|
+
latestCheckpoint {
|
|
4055
|
+
duoMessages {
|
|
4056
|
+
content
|
|
4057
|
+
correlationId
|
|
4058
|
+
role
|
|
4059
|
+
messageType
|
|
4060
|
+
status
|
|
4061
|
+
timestamp
|
|
4062
|
+
toolInfo
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
}`;
|
|
4068
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
4069
|
+
async function getWorkflowStatus(instanceUrl, token, workflowId) {
|
|
4070
|
+
const gid = `gid://gitlab/Ai::DuoWorkflows::Workflow/${workflowId}`;
|
|
4071
|
+
const maxWait = 12e4;
|
|
4072
|
+
const pollInterval = 5e3;
|
|
4073
|
+
const start = Date.now();
|
|
4074
|
+
while (Date.now() - start < maxWait) {
|
|
4075
|
+
const data2 = await gql(instanceUrl, token, WORKFLOW_STATUS_QUERY, { workflowId: gid });
|
|
4076
|
+
const nodes2 = data2?.duoWorkflowWorkflows?.nodes;
|
|
4077
|
+
if (!nodes2?.length) throw new Error(`Workflow ${workflowId} not found`);
|
|
4078
|
+
const workflow = nodes2[0];
|
|
4079
|
+
if (workflow.status !== "CREATED") return workflow;
|
|
4080
|
+
await sleep(pollInterval);
|
|
4081
|
+
}
|
|
4082
|
+
const data = await gql(instanceUrl, token, WORKFLOW_STATUS_QUERY, { workflowId: gid });
|
|
4083
|
+
const nodes = data?.duoWorkflowWorkflows?.nodes;
|
|
4084
|
+
if (!nodes?.length) throw new Error(`Workflow ${workflowId} not found`);
|
|
4085
|
+
return nodes[0];
|
|
4086
|
+
}
|
|
2731
4087
|
async function fetchCatalogAgents(instanceUrl, token, projectId) {
|
|
2732
4088
|
try {
|
|
2733
4089
|
const [foundational, custom] = await Promise.all([
|
|
@@ -2739,6 +4095,212 @@ async function fetchCatalogAgents(instanceUrl, token, projectId) {
|
|
|
2739
4095
|
return [];
|
|
2740
4096
|
}
|
|
2741
4097
|
}
|
|
4098
|
+
var LIST_AI_CATALOG_ITEMS_QUERY = `
|
|
4099
|
+
query listAiCatalogItems(
|
|
4100
|
+
$itemTypes: [AiCatalogItemType!]
|
|
4101
|
+
$search: String
|
|
4102
|
+
$first: Int
|
|
4103
|
+
$after: String
|
|
4104
|
+
) {
|
|
4105
|
+
aiCatalogItems(
|
|
4106
|
+
itemTypes: $itemTypes
|
|
4107
|
+
search: $search
|
|
4108
|
+
first: $first
|
|
4109
|
+
after: $after
|
|
4110
|
+
) {
|
|
4111
|
+
nodes {
|
|
4112
|
+
id
|
|
4113
|
+
name
|
|
4114
|
+
description
|
|
4115
|
+
itemType
|
|
4116
|
+
foundational
|
|
4117
|
+
public
|
|
4118
|
+
createdAt
|
|
4119
|
+
updatedAt
|
|
4120
|
+
softDeleted
|
|
4121
|
+
project { id, nameWithNamespace }
|
|
4122
|
+
latestVersion {
|
|
4123
|
+
id
|
|
4124
|
+
createdAt
|
|
4125
|
+
updatedAt
|
|
4126
|
+
createdBy { id, name, username }
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
pageInfo { hasNextPage, hasPreviousPage, startCursor, endCursor }
|
|
4130
|
+
}
|
|
4131
|
+
}`;
|
|
4132
|
+
var GET_AI_CATALOG_ITEM_QUERY = `
|
|
4133
|
+
query getAiCatalogItem($id: AiCatalogItemID!) {
|
|
4134
|
+
aiCatalogItem(id: $id) {
|
|
4135
|
+
id
|
|
4136
|
+
name
|
|
4137
|
+
description
|
|
4138
|
+
itemType
|
|
4139
|
+
foundational
|
|
4140
|
+
public
|
|
4141
|
+
createdAt
|
|
4142
|
+
updatedAt
|
|
4143
|
+
softDeleted
|
|
4144
|
+
project { id, nameWithNamespace }
|
|
4145
|
+
latestVersion {
|
|
4146
|
+
id
|
|
4147
|
+
createdAt
|
|
4148
|
+
updatedAt
|
|
4149
|
+
createdBy { id, name, username, webUrl }
|
|
4150
|
+
}
|
|
4151
|
+
userPermissions { adminAiCatalogItem, reportAiCatalogItem }
|
|
4152
|
+
}
|
|
4153
|
+
}`;
|
|
4154
|
+
var LIST_PROJECT_CONFIGURED_ITEMS_QUERY = `
|
|
4155
|
+
query listProjectConfiguredItems(
|
|
4156
|
+
$projectId: ProjectID!
|
|
4157
|
+
$itemTypes: [AiCatalogItemType!]
|
|
4158
|
+
$includeFoundationalConsumers: Boolean
|
|
4159
|
+
$first: Int
|
|
4160
|
+
$after: String
|
|
4161
|
+
) {
|
|
4162
|
+
aiCatalogConfiguredItems(
|
|
4163
|
+
projectId: $projectId
|
|
4164
|
+
itemTypes: $itemTypes
|
|
4165
|
+
includeFoundationalConsumers: $includeFoundationalConsumers
|
|
4166
|
+
first: $first
|
|
4167
|
+
after: $after
|
|
4168
|
+
) {
|
|
4169
|
+
nodes {
|
|
4170
|
+
id
|
|
4171
|
+
item {
|
|
4172
|
+
id
|
|
4173
|
+
name
|
|
4174
|
+
description
|
|
4175
|
+
itemType
|
|
4176
|
+
foundational
|
|
4177
|
+
public
|
|
4178
|
+
createdAt
|
|
4179
|
+
updatedAt
|
|
4180
|
+
softDeleted
|
|
4181
|
+
project { id, nameWithNamespace }
|
|
4182
|
+
latestVersion {
|
|
4183
|
+
id
|
|
4184
|
+
createdAt
|
|
4185
|
+
createdBy { name, username }
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
pinnedItemVersion { id, humanVersionName }
|
|
4189
|
+
}
|
|
4190
|
+
pageInfo { hasNextPage, hasPreviousPage, startCursor, endCursor }
|
|
4191
|
+
}
|
|
4192
|
+
}`;
|
|
4193
|
+
var ENABLE_AI_CATALOG_ITEM_MUTATION = `
|
|
4194
|
+
mutation enableAiCatalogItem($input: AiCatalogItemConsumerCreateInput!) {
|
|
4195
|
+
aiCatalogItemConsumerCreate(input: $input) {
|
|
4196
|
+
errors
|
|
4197
|
+
itemConsumer {
|
|
4198
|
+
id
|
|
4199
|
+
project { id, name, webUrl }
|
|
4200
|
+
group { id, name, webUrl }
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
}`;
|
|
4204
|
+
var DISABLE_AI_CATALOG_ITEM_MUTATION = `
|
|
4205
|
+
mutation disableAiCatalogItem($input: AiCatalogItemConsumerDeleteInput!) {
|
|
4206
|
+
aiCatalogItemConsumerDelete(input: $input) {
|
|
4207
|
+
errors
|
|
4208
|
+
success
|
|
4209
|
+
}
|
|
4210
|
+
}`;
|
|
4211
|
+
var FIND_ITEM_CONSUMER_FOR_DISABLE_QUERY = `
|
|
4212
|
+
query findItemConsumer(
|
|
4213
|
+
$projectId: ProjectID!
|
|
4214
|
+
$itemTypes: [AiCatalogItemType!]
|
|
4215
|
+
) {
|
|
4216
|
+
aiCatalogConfiguredItems(
|
|
4217
|
+
projectId: $projectId
|
|
4218
|
+
itemTypes: $itemTypes
|
|
4219
|
+
includeFoundationalConsumers: true
|
|
4220
|
+
first: 100
|
|
4221
|
+
) {
|
|
4222
|
+
nodes { id, item { id } }
|
|
4223
|
+
}
|
|
4224
|
+
}`;
|
|
4225
|
+
var RESOLVE_PROJECT_IDS_FOR_TOOLS_QUERY = `
|
|
4226
|
+
query resolveProjectIds($projectPath: ID!) {
|
|
4227
|
+
project(fullPath: $projectPath) {
|
|
4228
|
+
id
|
|
4229
|
+
namespace { id }
|
|
4230
|
+
}
|
|
4231
|
+
}`;
|
|
4232
|
+
function normalizeItemGid(id) {
|
|
4233
|
+
if (id.startsWith("gid://")) return id;
|
|
4234
|
+
if (!/^\d+$/.test(id)) throw new Error(`Invalid catalog item ID: "${id}"`);
|
|
4235
|
+
return `gid://gitlab/Ai::Catalog::Item/${id}`;
|
|
4236
|
+
}
|
|
4237
|
+
async function listAiCatalogItems(instanceUrl, token, itemTypes, options) {
|
|
4238
|
+
const variables = {
|
|
4239
|
+
itemTypes,
|
|
4240
|
+
first: options?.first ?? 20
|
|
4241
|
+
};
|
|
4242
|
+
if (options?.search) variables.search = options.search;
|
|
4243
|
+
if (options?.after) variables.after = options.after;
|
|
4244
|
+
const result = await gql(instanceUrl, token, LIST_AI_CATALOG_ITEMS_QUERY, variables);
|
|
4245
|
+
return result.aiCatalogItems;
|
|
4246
|
+
}
|
|
4247
|
+
async function getAiCatalogItem(instanceUrl, token, itemId) {
|
|
4248
|
+
const gid = normalizeItemGid(itemId);
|
|
4249
|
+
const result = await gql(instanceUrl, token, GET_AI_CATALOG_ITEM_QUERY, { id: gid });
|
|
4250
|
+
return result.aiCatalogItem;
|
|
4251
|
+
}
|
|
4252
|
+
async function resolveProjectGid(instanceUrl, token, projectPath) {
|
|
4253
|
+
const result = await gql(instanceUrl, token, RESOLVE_PROJECT_IDS_FOR_TOOLS_QUERY, {
|
|
4254
|
+
projectPath
|
|
4255
|
+
});
|
|
4256
|
+
return result.project.id;
|
|
4257
|
+
}
|
|
4258
|
+
async function listProjectAiCatalogItems(instanceUrl, token, projectPath, itemTypes, options) {
|
|
4259
|
+
const projectGid = await resolveProjectGid(instanceUrl, token, projectPath);
|
|
4260
|
+
const variables = {
|
|
4261
|
+
projectId: projectGid,
|
|
4262
|
+
itemTypes,
|
|
4263
|
+
includeFoundationalConsumers: true,
|
|
4264
|
+
first: options?.first ?? 20
|
|
4265
|
+
};
|
|
4266
|
+
if (options?.after) variables.after = options.after;
|
|
4267
|
+
const result = await gql(instanceUrl, token, LIST_PROJECT_CONFIGURED_ITEMS_QUERY, variables);
|
|
4268
|
+
return result.aiCatalogConfiguredItems;
|
|
4269
|
+
}
|
|
4270
|
+
async function enableAiCatalogItemForProject(instanceUrl, token, projectPath, itemId) {
|
|
4271
|
+
const projectGid = await resolveProjectGid(instanceUrl, token, projectPath);
|
|
4272
|
+
const gid = normalizeItemGid(itemId);
|
|
4273
|
+
const result = await gql(instanceUrl, token, ENABLE_AI_CATALOG_ITEM_MUTATION, {
|
|
4274
|
+
input: { itemId: gid, target: { projectId: projectGid } }
|
|
4275
|
+
});
|
|
4276
|
+
if (result.aiCatalogItemConsumerCreate.errors.length > 0) {
|
|
4277
|
+
throw new Error(
|
|
4278
|
+
`Failed to enable item: ${result.aiCatalogItemConsumerCreate.errors.join(", ")}`
|
|
4279
|
+
);
|
|
4280
|
+
}
|
|
4281
|
+
return result.aiCatalogItemConsumerCreate;
|
|
4282
|
+
}
|
|
4283
|
+
async function disableAiCatalogItemForProject(instanceUrl, token, projectPath, itemId) {
|
|
4284
|
+
const projectGid = await resolveProjectGid(instanceUrl, token, projectPath);
|
|
4285
|
+
const gid = normalizeItemGid(itemId);
|
|
4286
|
+
const consumerResult = await gql(instanceUrl, token, FIND_ITEM_CONSUMER_FOR_DISABLE_QUERY, {
|
|
4287
|
+
projectId: projectGid,
|
|
4288
|
+
itemTypes: ["AGENT", "FLOW", "THIRD_PARTY_FLOW"]
|
|
4289
|
+
});
|
|
4290
|
+
const consumer = consumerResult.aiCatalogConfiguredItems.nodes.find(
|
|
4291
|
+
(n) => n.item.id === gid
|
|
4292
|
+
);
|
|
4293
|
+
if (!consumer?.id) throw new Error("Agent/flow is not enabled in this project");
|
|
4294
|
+
const result = await gql(instanceUrl, token, DISABLE_AI_CATALOG_ITEM_MUTATION, {
|
|
4295
|
+
input: { id: consumer.id }
|
|
4296
|
+
});
|
|
4297
|
+
if (result.aiCatalogItemConsumerDelete.errors.length > 0) {
|
|
4298
|
+
throw new Error(
|
|
4299
|
+
`Failed to disable item: ${result.aiCatalogItemConsumerDelete.errors.join(", ")}`
|
|
4300
|
+
);
|
|
4301
|
+
}
|
|
4302
|
+
return result.aiCatalogItemConsumerDelete;
|
|
4303
|
+
}
|
|
2742
4304
|
|
|
2743
4305
|
// src/agents.ts
|
|
2744
4306
|
function resolveModelId(entry) {
|
|
@@ -2751,19 +4313,141 @@ function resolveModelId(entry) {
|
|
|
2751
4313
|
var import_fs = require("fs");
|
|
2752
4314
|
var import_path = require("path");
|
|
2753
4315
|
var import_os = __toESM(require("os"), 1);
|
|
2754
|
-
var
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
)
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
4316
|
+
var z = import_plugin.tool.schema;
|
|
4317
|
+
function makeItemTools(z2, itemType, label, getAuth, ensureAuth, onChanged) {
|
|
4318
|
+
const itemTypes = [itemType];
|
|
4319
|
+
const Label = label.charAt(0).toUpperCase() + label.slice(1);
|
|
4320
|
+
return {
|
|
4321
|
+
[`gitlab_list_${label}s`]: (0, import_plugin.tool)({
|
|
4322
|
+
description: `List ${label}s in the GitLab AI Catalog.
|
|
4323
|
+
Returns ${label}s with name, description, visibility, foundational flag, and version info.
|
|
4324
|
+
Supports search and cursor-based pagination.`,
|
|
4325
|
+
args: {
|
|
4326
|
+
search: z2.string().optional().describe(`Search query to filter ${label}s by name or description`),
|
|
4327
|
+
first: z2.number().optional().describe("Number of items to return (default 20)"),
|
|
4328
|
+
after: z2.string().optional().describe("Cursor for pagination (from pageInfo.endCursor)")
|
|
4329
|
+
},
|
|
4330
|
+
execute: async (args) => {
|
|
4331
|
+
const auth = ensureAuth();
|
|
4332
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4333
|
+
try {
|
|
4334
|
+
const result = await listAiCatalogItems(auth.instanceUrl, auth.token, itemTypes, args);
|
|
4335
|
+
return JSON.stringify(result, null, 2);
|
|
4336
|
+
} catch (err) {
|
|
4337
|
+
return `Error: ${err.message}`;
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
}),
|
|
4341
|
+
[`gitlab_get_${label}`]: (0, import_plugin.tool)({
|
|
4342
|
+
description: `Get details of a specific ${label} from the AI Catalog.
|
|
4343
|
+
Returns full details including name, description, visibility, versions, creator, and permissions.
|
|
4344
|
+
Accepts either a full Global ID (gid://gitlab/Ai::Catalog::Item/123) or just the numeric ID.`,
|
|
4345
|
+
args: {
|
|
4346
|
+
[`${label}_id`]: z2.string().describe(`${Label} ID: full GID or numeric ID`)
|
|
4347
|
+
},
|
|
4348
|
+
execute: async (args) => {
|
|
4349
|
+
const auth = ensureAuth();
|
|
4350
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4351
|
+
try {
|
|
4352
|
+
const result = await getAiCatalogItem(auth.instanceUrl, auth.token, args[`${label}_id`]);
|
|
4353
|
+
return JSON.stringify(result, null, 2);
|
|
4354
|
+
} catch (err) {
|
|
4355
|
+
return `Error: ${err.message}`;
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
}),
|
|
4359
|
+
[`gitlab_list_project_${label}s`]: (0, import_plugin.tool)({
|
|
4360
|
+
description: `List ${label}s enabled for a specific project.
|
|
4361
|
+
Returns all configured ${label}s including foundational ones.
|
|
4362
|
+
Each result includes the ${label} details and its consumer configuration.`,
|
|
4363
|
+
args: {
|
|
4364
|
+
project_id: z2.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
|
|
4365
|
+
first: z2.number().optional().describe("Number of items to return (default 20)"),
|
|
4366
|
+
after: z2.string().optional().describe("Cursor for pagination")
|
|
4367
|
+
},
|
|
4368
|
+
execute: async (args) => {
|
|
4369
|
+
const auth = ensureAuth();
|
|
4370
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4371
|
+
try {
|
|
4372
|
+
const result = await listProjectAiCatalogItems(
|
|
4373
|
+
auth.instanceUrl,
|
|
4374
|
+
auth.token,
|
|
4375
|
+
args.project_id,
|
|
4376
|
+
itemTypes,
|
|
4377
|
+
{ first: args.first, after: args.after }
|
|
4378
|
+
);
|
|
4379
|
+
return JSON.stringify(result, null, 2);
|
|
4380
|
+
} catch (err) {
|
|
4381
|
+
return `Error: ${err.message}`;
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
}),
|
|
4385
|
+
[`gitlab_enable_project_${label}`]: (0, import_plugin.tool)({
|
|
4386
|
+
description: `Enable a ${label} in a project.
|
|
4387
|
+
Requires Maintainer or Owner role.
|
|
4388
|
+
Enabling in a project also enables at the group level.
|
|
4389
|
+
Foundational ${label}s cannot be enabled this way (use admin settings).`,
|
|
4390
|
+
args: {
|
|
4391
|
+
project_id: z2.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
|
|
4392
|
+
[`${label}_id`]: z2.string().describe(`${Label} ID: full GID or numeric ID`)
|
|
4393
|
+
},
|
|
4394
|
+
execute: async (args) => {
|
|
4395
|
+
const auth = ensureAuth();
|
|
4396
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4397
|
+
try {
|
|
4398
|
+
const result = await enableAiCatalogItemForProject(
|
|
4399
|
+
auth.instanceUrl,
|
|
4400
|
+
auth.token,
|
|
4401
|
+
args.project_id,
|
|
4402
|
+
args[`${label}_id`]
|
|
4403
|
+
);
|
|
4404
|
+
await onChanged?.();
|
|
4405
|
+
return JSON.stringify(result, null, 2) + "\n\nNote: Restart opencode for the @ menu to reflect changes.";
|
|
4406
|
+
} catch (err) {
|
|
4407
|
+
return `Error: ${err.message}`;
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
}),
|
|
4411
|
+
[`gitlab_disable_project_${label}`]: (0, import_plugin.tool)({
|
|
4412
|
+
description: `Disable a ${label} in a project.
|
|
4413
|
+
Requires Maintainer or Owner role.
|
|
4414
|
+
Resolves the consumer ID internally from the ${label} ID.`,
|
|
4415
|
+
args: {
|
|
4416
|
+
project_id: z2.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
|
|
4417
|
+
[`${label}_id`]: z2.string().describe(`${Label} ID: full GID or numeric ID`)
|
|
4418
|
+
},
|
|
4419
|
+
execute: async (args) => {
|
|
4420
|
+
const auth = ensureAuth();
|
|
4421
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4422
|
+
try {
|
|
4423
|
+
const result = await disableAiCatalogItemForProject(
|
|
4424
|
+
auth.instanceUrl,
|
|
4425
|
+
auth.token,
|
|
4426
|
+
args.project_id,
|
|
4427
|
+
args[`${label}_id`]
|
|
4428
|
+
);
|
|
4429
|
+
await onChanged?.();
|
|
4430
|
+
return JSON.stringify(result, null, 2) + "\n\nNote: Restart opencode for the @ menu to reflect changes.";
|
|
4431
|
+
} catch (err) {
|
|
4432
|
+
return `Error: ${err.message}`;
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
})
|
|
4436
|
+
};
|
|
4437
|
+
}
|
|
4438
|
+
function makeAgentFlowTools(z2, getAuth, readAuthFn, setAuth, onChanged) {
|
|
4439
|
+
const ensureAuth = () => {
|
|
4440
|
+
let auth = getAuth();
|
|
4441
|
+
if (!auth) {
|
|
4442
|
+
auth = readAuthFn();
|
|
4443
|
+
if (auth) setAuth(auth);
|
|
4444
|
+
}
|
|
4445
|
+
return auth;
|
|
4446
|
+
};
|
|
4447
|
+
return {
|
|
4448
|
+
...makeItemTools(z2, "AGENT", "agent", getAuth, ensureAuth, onChanged),
|
|
4449
|
+
...makeItemTools(z2, "FLOW", "flow", getAuth, ensureAuth, onChanged)
|
|
4450
|
+
};
|
|
2767
4451
|
}
|
|
2768
4452
|
function readAuth() {
|
|
2769
4453
|
try {
|
|
@@ -2781,54 +4465,300 @@ function readAuth() {
|
|
|
2781
4465
|
}
|
|
2782
4466
|
var memo = /* @__PURE__ */ new Map();
|
|
2783
4467
|
var plugin = async (input) => {
|
|
2784
|
-
|
|
4468
|
+
let authCache = null;
|
|
4469
|
+
let projectPath;
|
|
4470
|
+
let namespaceId;
|
|
4471
|
+
const flowAgents = /* @__PURE__ */ new Map();
|
|
4472
|
+
let cfgRef = null;
|
|
4473
|
+
let baseModelIdRef;
|
|
2785
4474
|
async function load2() {
|
|
2786
4475
|
const auth = readAuth();
|
|
2787
|
-
log("readAuth result:", auth ? `instanceUrl=${auth.instanceUrl}` : "null");
|
|
2788
4476
|
if (!auth) return null;
|
|
4477
|
+
authCache = auth;
|
|
2789
4478
|
const { token, instanceUrl } = auth;
|
|
2790
|
-
log("instanceUrl:", instanceUrl);
|
|
2791
4479
|
const cache = new import_gitlab_ai_provider.GitLabModelCache(input.directory, instanceUrl);
|
|
2792
4480
|
const entry = cache.load();
|
|
2793
|
-
log(
|
|
2794
|
-
"cache entry:",
|
|
2795
|
-
entry ? `discovery=${!!entry.discovery} projectId=${entry.project?.id}` : "null"
|
|
2796
|
-
);
|
|
2797
4481
|
if (!entry?.discovery) return null;
|
|
2798
4482
|
const projectId = entry.project?.id;
|
|
2799
4483
|
if (!projectId) return null;
|
|
4484
|
+
projectPath = entry.project?.pathWithNamespace;
|
|
4485
|
+
namespaceId = entry.project?.namespaceId;
|
|
2800
4486
|
const key = `${input.directory}\0${instanceUrl}`;
|
|
2801
4487
|
const cached = memo.get(key);
|
|
2802
4488
|
const agents = cached ?? await fetchCatalogAgents(instanceUrl, token, `gid://gitlab/Project/${projectId}`);
|
|
2803
|
-
log("agents fetched:", agents.length);
|
|
2804
4489
|
if (!cached) memo.set(key, agents);
|
|
2805
4490
|
return { agents, entry };
|
|
2806
4491
|
}
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
4492
|
+
async function refreshAgents() {
|
|
4493
|
+
if (!authCache || !cfgRef) return;
|
|
4494
|
+
const key = `${input.directory}\0${authCache.instanceUrl}`;
|
|
4495
|
+
memo.delete(key);
|
|
4496
|
+
const result = await load2();
|
|
4497
|
+
if (!result) return;
|
|
4498
|
+
const previousNames = /* @__PURE__ */ new Set([
|
|
4499
|
+
...flowAgents.keys(),
|
|
4500
|
+
...Object.keys(cfgRef.agent ?? {}).filter((n) => {
|
|
4501
|
+
const desc = cfgRef.agent[n]?.description ?? "";
|
|
4502
|
+
return desc.startsWith("[GitLab");
|
|
4503
|
+
})
|
|
4504
|
+
]);
|
|
4505
|
+
flowAgents.clear();
|
|
4506
|
+
const currentNames = /* @__PURE__ */ new Set();
|
|
4507
|
+
for (const agent of result.agents) {
|
|
4508
|
+
currentNames.add(agent.name);
|
|
4509
|
+
const isFlow = agent.itemType === "FLOW";
|
|
4510
|
+
if (isFlow && agent.consumerId && projectPath) {
|
|
4511
|
+
flowAgents.set(agent.name, agent);
|
|
4512
|
+
cfgRef.agent[agent.name] = {
|
|
4513
|
+
name: agent.name,
|
|
4514
|
+
description: `[GitLab Flow] ${agent.description}`,
|
|
4515
|
+
mode: "subagent"
|
|
4516
|
+
};
|
|
4517
|
+
} else {
|
|
4518
|
+
cfgRef.agent[agent.name] = {
|
|
2816
4519
|
name: agent.name,
|
|
2817
4520
|
description: agent.foundational ? "[GitLab Foundational Agent]" : "[GitLab Custom Agent]",
|
|
2818
4521
|
mode: "primary",
|
|
2819
|
-
model: `gitlab/${
|
|
4522
|
+
model: `gitlab/${baseModelIdRef}`,
|
|
2820
4523
|
options: {
|
|
2821
4524
|
workflowDefinition: agent.workflowDefinition,
|
|
2822
4525
|
flowConfig: agent.flowConfig,
|
|
2823
4526
|
flowConfigSchemaVersion: agent.flowConfigSchemaVersion,
|
|
2824
|
-
// numeric version ID passed as ai_catalog_item_version_id in createWorkflow
|
|
2825
|
-
// so GitLab UI links the session to the correct custom agent page
|
|
2826
4527
|
aiCatalogItemVersionId: agent.catalogItemVersionId
|
|
2827
4528
|
},
|
|
2828
4529
|
permission: { "*": "allow" }
|
|
2829
4530
|
};
|
|
2830
4531
|
}
|
|
2831
|
-
|
|
4532
|
+
}
|
|
4533
|
+
for (const name of previousNames) {
|
|
4534
|
+
if (!currentNames.has(name)) {
|
|
4535
|
+
delete cfgRef.agent[name];
|
|
4536
|
+
}
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
return {
|
|
4540
|
+
async config(cfg) {
|
|
4541
|
+
const result = await load2();
|
|
4542
|
+
if (!result?.agents.length) return;
|
|
4543
|
+
const baseModelId = resolveModelId(result.entry);
|
|
4544
|
+
cfg.agent ??= {};
|
|
4545
|
+
cfgRef = cfg;
|
|
4546
|
+
baseModelIdRef = baseModelId;
|
|
4547
|
+
for (const agent of result.agents) {
|
|
4548
|
+
const isFlow = agent.itemType === "FLOW";
|
|
4549
|
+
if (isFlow && agent.consumerId && projectPath) {
|
|
4550
|
+
flowAgents.set(agent.name, agent);
|
|
4551
|
+
cfg.agent[agent.name] = {
|
|
4552
|
+
name: agent.name,
|
|
4553
|
+
description: `[GitLab Flow] ${agent.description}`,
|
|
4554
|
+
mode: "subagent"
|
|
4555
|
+
};
|
|
4556
|
+
} else {
|
|
4557
|
+
cfg.agent[agent.name] = {
|
|
4558
|
+
name: agent.name,
|
|
4559
|
+
description: agent.foundational ? "[GitLab Foundational Agent]" : "[GitLab Custom Agent]",
|
|
4560
|
+
mode: "primary",
|
|
4561
|
+
model: `gitlab/${baseModelId}`,
|
|
4562
|
+
options: {
|
|
4563
|
+
workflowDefinition: agent.workflowDefinition,
|
|
4564
|
+
flowConfig: agent.flowConfig,
|
|
4565
|
+
flowConfigSchemaVersion: agent.flowConfigSchemaVersion,
|
|
4566
|
+
aiCatalogItemVersionId: agent.catalogItemVersionId
|
|
4567
|
+
},
|
|
4568
|
+
permission: { "*": "allow" }
|
|
4569
|
+
};
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
},
|
|
4573
|
+
"chat.message": async (_input, output) => {
|
|
4574
|
+
const indicesToRemove = [];
|
|
4575
|
+
const replacements = [];
|
|
4576
|
+
for (let i = 0; i < output.parts.length; i++) {
|
|
4577
|
+
const part = output.parts[i];
|
|
4578
|
+
if (part.type !== "agent") continue;
|
|
4579
|
+
const flow = flowAgents.get(part.name);
|
|
4580
|
+
if (!flow || !flow.consumerId || !projectPath) continue;
|
|
4581
|
+
const rawText = output.parts.filter((p) => p.type === "text" && !p.synthetic).map((p) => p.text).join(" ").trim() || "Execute the flow";
|
|
4582
|
+
const baseUrl = authCache?.instanceUrl?.replace(/\/$/, "") ?? "https://gitlab.com";
|
|
4583
|
+
const projectUrl = `${baseUrl}/${projectPath}`;
|
|
4584
|
+
const subagentPrompt = [
|
|
4585
|
+
`Execute the "${flow.name}" GitLab flow on project ${projectPath} (${projectUrl}).`,
|
|
4586
|
+
`User goal: "${rawText}"`,
|
|
4587
|
+
``,
|
|
4588
|
+
`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.`,
|
|
4589
|
+
``,
|
|
4590
|
+
`STEP 1 - FETCH AND DISPLAY FLOW DEFINITION:`,
|
|
4591
|
+
`Call gitlab_get_flow_definition with consumer_id: ${flow.consumerId} and foundational: ${!!flow.foundational}.`,
|
|
4592
|
+
`The response has a "config" field containing YAML. Parse it and find the "flow:" \u2192 "inputs:" section.`,
|
|
4593
|
+
`Each input has a "category" and "input_schema" with field names and types.`,
|
|
4594
|
+
`Print ALL inputs you found. Skip "agent_platform_standard_context" (auto-injected by server).`,
|
|
4595
|
+
`If the config has no "inputs" or "inputs: []", say "No required inputs" and skip to step 3.`,
|
|
4596
|
+
``,
|
|
4597
|
+
`STEP 2 - GATHER INPUT VALUES:`,
|
|
4598
|
+
`For each required input from step 1, be creative and resourceful in finding the real values:`,
|
|
4599
|
+
`- Use ANY available tools to find the data: GitLab tools, MCP servers, search, file reads, etc.`,
|
|
4600
|
+
`- Look at the user's goal for hints (MR numbers, pipeline IDs, branch names, URLs)`,
|
|
4601
|
+
`- For URL fields: construct full GitLab URLs using base ${projectUrl}`,
|
|
4602
|
+
`- For branch fields: look up from MRs, pipelines, or project default branch`,
|
|
4603
|
+
`- For IDs: extract from the user's message or look up via GitLab API`,
|
|
4604
|
+
`- If an input cannot be determined, ask yourself what makes sense given the context`,
|
|
4605
|
+
`Print each resolved input value before proceeding.`,
|
|
4606
|
+
``,
|
|
4607
|
+
`STEP 3 - EXECUTE THE FLOW:`,
|
|
4608
|
+
`Call gitlab_execute_project_flow with:`,
|
|
4609
|
+
`- project_id: "${projectPath}"`,
|
|
4610
|
+
`- consumer_id: ${flow.consumerId}`,
|
|
4611
|
+
`- goal: the user's goal with any relevant URLs/context appended`,
|
|
4612
|
+
`- additional_context: JSON array of inputs from step 2, each entry: {"Category":"<category>","Content":"{\\"field\\":\\"value\\"}"}`,
|
|
4613
|
+
` Example: [{"Category":"merge_request","Content":"{\\"url\\":\\"${projectUrl}/-/merge_requests/12\\"}"}]`,
|
|
4614
|
+
``,
|
|
4615
|
+
`STEP 4 - CONFIRM FLOW STARTED:`,
|
|
4616
|
+
`Call gitlab_get_workflow_status with the returned workflow_id.`,
|
|
4617
|
+
`The tool blocks automatically while status is CREATED (up to 2 minutes).`,
|
|
4618
|
+
`Once it returns, report:`,
|
|
4619
|
+
`- The workflow status (RUNNING, FINISHED, FAILED, etc.)`,
|
|
4620
|
+
`- The workflow URL: ${projectUrl}/-/duo_workflows/<workflow_id>`,
|
|
4621
|
+
`- A brief summary of what was executed`,
|
|
4622
|
+
`Then stop. Do NOT keep polling. The user can follow the flow execution in the GitLab UI.`
|
|
4623
|
+
].join("\n");
|
|
4624
|
+
const resultText = [
|
|
4625
|
+
`IMPORTANT: You MUST call the Task tool RIGHT NOW to dispatch a subagent. Do NOT execute these steps yourself.`,
|
|
4626
|
+
``,
|
|
4627
|
+
`Call the Task tool with:`,
|
|
4628
|
+
` description: "Execute ${flow.name} flow"`,
|
|
4629
|
+
` prompt: ${JSON.stringify(subagentPrompt)}`,
|
|
4630
|
+
``,
|
|
4631
|
+
`Do not do anything else. Just call the Task tool with the above parameters.`
|
|
4632
|
+
].join("\n");
|
|
4633
|
+
replacements.push({ idx: i, text: resultText });
|
|
4634
|
+
if (i + 1 < output.parts.length) {
|
|
4635
|
+
const next = output.parts[i + 1];
|
|
4636
|
+
if (next.type === "text" && next.synthetic && next.text?.includes("call the task tool with subagent")) {
|
|
4637
|
+
indicesToRemove.push(i + 1);
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
for (const { idx, text } of replacements) {
|
|
4642
|
+
const original = output.parts[idx];
|
|
4643
|
+
output.parts[idx] = {
|
|
4644
|
+
...original,
|
|
4645
|
+
type: "text",
|
|
4646
|
+
text
|
|
4647
|
+
};
|
|
4648
|
+
delete output.parts[idx].name;
|
|
4649
|
+
delete output.parts[idx].source;
|
|
4650
|
+
}
|
|
4651
|
+
for (const idx of indicesToRemove.reverse()) {
|
|
4652
|
+
output.parts.splice(idx, 1);
|
|
4653
|
+
}
|
|
4654
|
+
},
|
|
4655
|
+
tool: {
|
|
4656
|
+
gitlab_execute_project_flow: (0, import_plugin.tool)({
|
|
4657
|
+
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.",
|
|
4658
|
+
args: {
|
|
4659
|
+
project_id: z.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
|
|
4660
|
+
consumer_id: z.number().describe("AI Catalog ItemConsumer numeric ID"),
|
|
4661
|
+
goal: z.string().describe("User prompt/goal for the flow, include relevant URLs"),
|
|
4662
|
+
additional_context: z.string().optional().describe(
|
|
4663
|
+
'JSON array of flow inputs: [{"Category":"merge_request","Content":"{\\"url\\":\\"https://...\\"}"}]'
|
|
4664
|
+
),
|
|
4665
|
+
issue_id: z.number().optional().describe("Issue IID for context"),
|
|
4666
|
+
merge_request_id: z.number().optional().describe("Merge request IID for context")
|
|
4667
|
+
},
|
|
4668
|
+
execute: async (args) => {
|
|
4669
|
+
if (!authCache) {
|
|
4670
|
+
const auth = readAuth();
|
|
4671
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4672
|
+
authCache = auth;
|
|
4673
|
+
}
|
|
4674
|
+
let flowInputs;
|
|
4675
|
+
if (args.additional_context) {
|
|
4676
|
+
try {
|
|
4677
|
+
flowInputs = JSON.parse(args.additional_context);
|
|
4678
|
+
} catch {
|
|
4679
|
+
return "Error: additional_context must be a valid JSON array";
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
try {
|
|
4683
|
+
const result = await executeFlow(
|
|
4684
|
+
authCache.instanceUrl,
|
|
4685
|
+
authCache.token,
|
|
4686
|
+
args.project_id,
|
|
4687
|
+
args.consumer_id,
|
|
4688
|
+
args.goal,
|
|
4689
|
+
{
|
|
4690
|
+
issueId: args.issue_id,
|
|
4691
|
+
mergeRequestId: args.merge_request_id,
|
|
4692
|
+
namespaceId,
|
|
4693
|
+
flowInputs
|
|
4694
|
+
}
|
|
4695
|
+
);
|
|
4696
|
+
return JSON.stringify(result, null, 2);
|
|
4697
|
+
} catch (err) {
|
|
4698
|
+
return `Error executing flow: ${err.message}`;
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4701
|
+
}),
|
|
4702
|
+
gitlab_get_flow_definition: (0, import_plugin.tool)({
|
|
4703
|
+
description: "Get the YAML configuration of a GitLab DAP flow.\nReturns the flow config YAML which contains the flow.inputs section\ndescribing what additional_context categories and fields the flow requires.\nUse this before executing a flow to understand what inputs to gather.\nSet foundational=true for GitLab built-in flows, foundational=false for custom flows.",
|
|
4704
|
+
args: {
|
|
4705
|
+
consumer_id: z.number().describe("AI Catalog ItemConsumer numeric ID"),
|
|
4706
|
+
foundational: z.boolean().describe("true for GitLab foundational flows, false for custom flows")
|
|
4707
|
+
},
|
|
4708
|
+
execute: async (args) => {
|
|
4709
|
+
if (!authCache) {
|
|
4710
|
+
const auth = readAuth();
|
|
4711
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4712
|
+
authCache = auth;
|
|
4713
|
+
}
|
|
4714
|
+
const flow = [...flowAgents.values()].find((f) => f.consumerId === args.consumer_id);
|
|
4715
|
+
try {
|
|
4716
|
+
const result = await getFlowDefinition(authCache.instanceUrl, authCache.token, {
|
|
4717
|
+
consumerId: args.consumer_id,
|
|
4718
|
+
flowName: flow?.name,
|
|
4719
|
+
foundational: args.foundational,
|
|
4720
|
+
catalogItemVersionId: flow?.catalogItemVersionId,
|
|
4721
|
+
itemIdentifier: flow?.identifier,
|
|
4722
|
+
workflowDefinition: flow?.workflowDefinition
|
|
4723
|
+
});
|
|
4724
|
+
return JSON.stringify(result, null, 2);
|
|
4725
|
+
} catch (err) {
|
|
4726
|
+
return `Error getting flow definition: ${err.message}`;
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4729
|
+
}),
|
|
4730
|
+
gitlab_get_workflow_status: (0, import_plugin.tool)({
|
|
4731
|
+
description: "Get the status and latest messages of a GitLab DAP workflow.\nUse this to monitor a running flow after executing it.\nReturns the workflow status, latest checkpoint messages, and timestamps.\nPoll every 10 seconds until status is completed, failed, or cancelled.",
|
|
4732
|
+
args: {
|
|
4733
|
+
workflow_id: z.number().describe("Workflow numeric ID (from gitlab_execute_project_flow result)")
|
|
4734
|
+
},
|
|
4735
|
+
execute: async (args) => {
|
|
4736
|
+
if (!authCache) {
|
|
4737
|
+
const auth = readAuth();
|
|
4738
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4739
|
+
authCache = auth;
|
|
4740
|
+
}
|
|
4741
|
+
try {
|
|
4742
|
+
const result = await getWorkflowStatus(
|
|
4743
|
+
authCache.instanceUrl,
|
|
4744
|
+
authCache.token,
|
|
4745
|
+
args.workflow_id
|
|
4746
|
+
);
|
|
4747
|
+
return JSON.stringify(result, null, 2);
|
|
4748
|
+
} catch (err) {
|
|
4749
|
+
return `Error getting workflow status: ${err.message}`;
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
}),
|
|
4753
|
+
...makeAgentFlowTools(
|
|
4754
|
+
z,
|
|
4755
|
+
() => authCache,
|
|
4756
|
+
readAuth,
|
|
4757
|
+
(a) => {
|
|
4758
|
+
authCache = a;
|
|
4759
|
+
},
|
|
4760
|
+
refreshAgents
|
|
4761
|
+
)
|
|
2832
4762
|
}
|
|
2833
4763
|
};
|
|
2834
4764
|
};
|