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.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
3
|
import { GitLabModelCache } from "gitlab-ai-provider";
|
|
3
4
|
|
|
4
5
|
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
@@ -2587,7 +2588,1195 @@ var safeLoad = renamed("safeLoad", "load");
|
|
|
2587
2588
|
var safeLoadAll = renamed("safeLoadAll", "loadAll");
|
|
2588
2589
|
var safeDump = renamed("safeDump", "dump");
|
|
2589
2590
|
|
|
2591
|
+
// src/generated/foundational-flows.ts
|
|
2592
|
+
var FOUNDATIONAL_FLOWS = {
|
|
2593
|
+
"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',
|
|
2594
|
+
"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',
|
|
2595
|
+
"convert_to_gl_ci": `name: "Convert to GitLab CI"
|
|
2596
|
+
description: |
|
|
2597
|
+
Converts Jenkins pipelines (Jenkinsfile) to GitLab CI/CD configuration files (.gitlab-ci.yml).
|
|
2598
|
+
This flow reads a Jenkins configuration file, translates it to GitLab CI format with validation,
|
|
2599
|
+
and creates a merge request with the converted configuration.
|
|
2600
|
+
product_group: agent_foundations
|
|
2601
|
+
version: "v1"
|
|
2602
|
+
environment: ambient
|
|
2603
|
+
|
|
2604
|
+
components:
|
|
2605
|
+
- name: "load_jenkins_file"
|
|
2606
|
+
type: DeterministicStepComponent
|
|
2607
|
+
inputs:
|
|
2608
|
+
- from: "context:goal"
|
|
2609
|
+
as: "file_path"
|
|
2610
|
+
tool_name: "read_file"
|
|
2611
|
+
ui_log_events:
|
|
2612
|
+
- "on_tool_execution_success"
|
|
2613
|
+
- "on_tool_execution_failed"
|
|
2614
|
+
|
|
2615
|
+
- name: "convert_to_gitlab_ci"
|
|
2616
|
+
type: AgentComponent
|
|
2617
|
+
prompt_id: "convert_to_gl_ci"
|
|
2618
|
+
prompt_version: "^1.0.0"
|
|
2619
|
+
inputs:
|
|
2620
|
+
- from: "context:load_jenkins_file.tool_responses"
|
|
2621
|
+
as: "jenkins_file_content"
|
|
2622
|
+
toolset:
|
|
2623
|
+
- "create_file_with_contents"
|
|
2624
|
+
- "read_file"
|
|
2625
|
+
ui_log_events:
|
|
2626
|
+
- "on_agent_final_answer"
|
|
2627
|
+
- "on_tool_execution_success"
|
|
2628
|
+
- "on_tool_execution_failed"
|
|
2629
|
+
|
|
2630
|
+
- name: "create_repository_branch"
|
|
2631
|
+
type: AgentComponent
|
|
2632
|
+
prompt_id: "create_repository_branch"
|
|
2633
|
+
prompt_version: "^1.0.0"
|
|
2634
|
+
toolset:
|
|
2635
|
+
- "run_git_command"
|
|
2636
|
+
inputs:
|
|
2637
|
+
- from: "context:project_id"
|
|
2638
|
+
as: "project_id"
|
|
2639
|
+
- from: "context:inputs.agent_platform_standard_context.primary_branch"
|
|
2640
|
+
as: "ref"
|
|
2641
|
+
- from: "context:project_http_url_to_repo"
|
|
2642
|
+
as: "repository_url"
|
|
2643
|
+
- from: "Duo Agent: Convert to GitLab CI"
|
|
2644
|
+
as: "naming_context"
|
|
2645
|
+
literal: True
|
|
2646
|
+
- from: "context:workflow_id"
|
|
2647
|
+
as: "workflow_id"
|
|
2648
|
+
- from: "context:inputs.user_rule"
|
|
2649
|
+
as: "agents_dot_md"
|
|
2650
|
+
optional: True
|
|
2651
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2652
|
+
as: "workspace_agent_skills"
|
|
2653
|
+
optional: True
|
|
2654
|
+
ui_log_events:
|
|
2655
|
+
- "on_tool_execution_success"
|
|
2656
|
+
- "on_tool_execution_failed"
|
|
2657
|
+
|
|
2658
|
+
- name: "git_commit"
|
|
2659
|
+
type: AgentComponent
|
|
2660
|
+
prompt_id: "commit_changes"
|
|
2661
|
+
prompt_version: "^1.0.0"
|
|
2662
|
+
inputs:
|
|
2663
|
+
- from: "context:project_http_url_to_repo"
|
|
2664
|
+
as: "repository_url"
|
|
2665
|
+
- from: "context:inputs.user_rule"
|
|
2666
|
+
as: "agents_dot_md"
|
|
2667
|
+
optional: True
|
|
2668
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2669
|
+
as: "workspace_agent_skills"
|
|
2670
|
+
optional: True
|
|
2671
|
+
toolset:
|
|
2672
|
+
- "run_git_command"
|
|
2673
|
+
ui_log_events:
|
|
2674
|
+
- "on_tool_execution_success"
|
|
2675
|
+
- "on_tool_execution_failed"
|
|
2676
|
+
|
|
2677
|
+
- name: "git_push"
|
|
2678
|
+
type: AgentComponent
|
|
2679
|
+
prompt_id: "convert_ci_push_changes"
|
|
2680
|
+
prompt_version: "^1.0.0"
|
|
2681
|
+
inputs:
|
|
2682
|
+
- from: "context:project_http_url_to_repo"
|
|
2683
|
+
as: "repository_url"
|
|
2684
|
+
- from: "context:create_repository_branch.final_answer"
|
|
2685
|
+
as: "target_branch_details"
|
|
2686
|
+
- from: "context:inputs.agent_platform_standard_context.primary_branch"
|
|
2687
|
+
as: "source_branch"
|
|
2688
|
+
- from: "context:project_id"
|
|
2689
|
+
as: "project_id"
|
|
2690
|
+
- from: "context:workflow_id"
|
|
2691
|
+
as: "workflow_id"
|
|
2692
|
+
- from: "context:session_url"
|
|
2693
|
+
as: "session_url"
|
|
2694
|
+
- from: "context:inputs.user_rule"
|
|
2695
|
+
as: "agents_dot_md"
|
|
2696
|
+
optional: True
|
|
2697
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2698
|
+
as: "workspace_agent_skills"
|
|
2699
|
+
optional: True
|
|
2700
|
+
toolset:
|
|
2701
|
+
- "run_git_command"
|
|
2702
|
+
- "create_merge_request"
|
|
2703
|
+
ui_log_events:
|
|
2704
|
+
- "on_agent_final_answer"
|
|
2705
|
+
- "on_tool_execution_success"
|
|
2706
|
+
- "on_tool_execution_failed"
|
|
2707
|
+
|
|
2708
|
+
routers:
|
|
2709
|
+
- from: "load_jenkins_file"
|
|
2710
|
+
to: "convert_to_gitlab_ci"
|
|
2711
|
+
- from: "convert_to_gitlab_ci"
|
|
2712
|
+
to: "create_repository_branch"
|
|
2713
|
+
- from: "create_repository_branch"
|
|
2714
|
+
to: "git_commit"
|
|
2715
|
+
- from: "git_commit"
|
|
2716
|
+
to: "git_push"
|
|
2717
|
+
- from: "git_push"
|
|
2718
|
+
to: "end"
|
|
2719
|
+
|
|
2720
|
+
flow:
|
|
2721
|
+
entry_point: "load_jenkins_file"
|
|
2722
|
+
inputs:
|
|
2723
|
+
- category: agent_user_environment
|
|
2724
|
+
input_schema:
|
|
2725
|
+
source_branch:
|
|
2726
|
+
type: string
|
|
2727
|
+
description: Source branch of the Jenkins file
|
|
2728
|
+
- category: agent_platform_standard_context
|
|
2729
|
+
input_schema:
|
|
2730
|
+
workload_branch:
|
|
2731
|
+
type: string
|
|
2732
|
+
description: The workload branch for the GitLab Merge Request
|
|
2733
|
+
primary_branch:
|
|
2734
|
+
type: string
|
|
2735
|
+
description: Merge Request target branch
|
|
2736
|
+
session_owner_id:
|
|
2737
|
+
type: string
|
|
2738
|
+
description: Human user's ID that initiated the flow
|
|
2739
|
+
`,
|
|
2740
|
+
"developer": `version: "v1"
|
|
2741
|
+
environment: ambient
|
|
2742
|
+
|
|
2743
|
+
components:
|
|
2744
|
+
- name: "issue_parser"
|
|
2745
|
+
type: DeterministicStepComponent
|
|
2746
|
+
tool_name: "get_issue"
|
|
2747
|
+
inputs:
|
|
2748
|
+
- from: "context:goal"
|
|
2749
|
+
as: "url"
|
|
2750
|
+
ui_log_events:
|
|
2751
|
+
- "on_tool_execution_success"
|
|
2752
|
+
- "on_tool_execution_failed"
|
|
2753
|
+
- name: "create_repository_branch"
|
|
2754
|
+
type: AgentComponent
|
|
2755
|
+
prompt_id: "create_repository_branch"
|
|
2756
|
+
prompt_version: "^1.0.0"
|
|
2757
|
+
toolset:
|
|
2758
|
+
- "run_git_command"
|
|
2759
|
+
model_size_preference: "small"
|
|
2760
|
+
inputs:
|
|
2761
|
+
- from: "context:project_id"
|
|
2762
|
+
as: "project_id"
|
|
2763
|
+
- from: "context:inputs.agent_platform_standard_context.primary_branch"
|
|
2764
|
+
as: "ref"
|
|
2765
|
+
- from: "context:project_http_url_to_repo"
|
|
2766
|
+
as: "repository_url"
|
|
2767
|
+
- from: "context:issue_parser.tool_responses"
|
|
2768
|
+
as: "naming_context"
|
|
2769
|
+
- from: "context:workflow_id"
|
|
2770
|
+
as: "workflow_id"
|
|
2771
|
+
- from: "context:inputs.user_rule"
|
|
2772
|
+
as: "agents_dot_md"
|
|
2773
|
+
optional: True
|
|
2774
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2775
|
+
as: "workspace_agent_skills"
|
|
2776
|
+
optional: True
|
|
2777
|
+
ui_log_events:
|
|
2778
|
+
- "on_tool_execution_success"
|
|
2779
|
+
- "on_tool_execution_failed"
|
|
2780
|
+
- name: "draft_merge_request_creator"
|
|
2781
|
+
type: OneOffComponent
|
|
2782
|
+
prompt_id: "create_merge_request"
|
|
2783
|
+
prompt_version: "^1.0.0"
|
|
2784
|
+
tool_name: "create_merge_request"
|
|
2785
|
+
model_size_preference: "small"
|
|
2786
|
+
inputs:
|
|
2787
|
+
- from: "context:goal"
|
|
2788
|
+
as: "issue_url"
|
|
2789
|
+
- from: "context:project_id"
|
|
2790
|
+
as: "project_id"
|
|
2791
|
+
- from: "context:issue_parser.tool_responses"
|
|
2792
|
+
as: "issue_details"
|
|
2793
|
+
- from: "context:create_repository_branch.final_answer"
|
|
2794
|
+
as: "source_branch_details"
|
|
2795
|
+
- from: "context:inputs.agent_platform_standard_context.primary_branch"
|
|
2796
|
+
as: "target_branch"
|
|
2797
|
+
- from: "context:inputs.user_rule"
|
|
2798
|
+
as: "agents_dot_md"
|
|
2799
|
+
optional: True
|
|
2800
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2801
|
+
as: "workspace_agent_skills"
|
|
2802
|
+
optional: True
|
|
2803
|
+
ui_log_events:
|
|
2804
|
+
- "on_tool_call_input"
|
|
2805
|
+
- "on_tool_execution_success"
|
|
2806
|
+
- "on_tool_execution_failed"
|
|
2807
|
+
- name: "tool_listing_agent"
|
|
2808
|
+
type: AgentComponent
|
|
2809
|
+
prompt_id: "list_available_tools"
|
|
2810
|
+
prompt_version: "^1.0.0"
|
|
2811
|
+
model_size_preference: "large"
|
|
2812
|
+
toolset:
|
|
2813
|
+
- "list_issues"
|
|
2814
|
+
- "get_issue"
|
|
2815
|
+
- "list_issue_notes"
|
|
2816
|
+
- "get_issue_note"
|
|
2817
|
+
- "get_job_logs"
|
|
2818
|
+
- "get_merge_request"
|
|
2819
|
+
- "get_pipeline_failing_jobs"
|
|
2820
|
+
- "get_project"
|
|
2821
|
+
- "list_all_merge_request_notes"
|
|
2822
|
+
- "list_merge_request_diffs"
|
|
2823
|
+
- "gitlab_issue_search"
|
|
2824
|
+
- "gitlab_merge_request_search"
|
|
2825
|
+
- "read_file"
|
|
2826
|
+
- "create_file_with_contents"
|
|
2827
|
+
- "edit_file"
|
|
2828
|
+
- "find_files"
|
|
2829
|
+
- "grep"
|
|
2830
|
+
- "mkdir"
|
|
2831
|
+
- "get_epic"
|
|
2832
|
+
- "list_epics"
|
|
2833
|
+
- "get_repository_file"
|
|
2834
|
+
- "list_dir"
|
|
2835
|
+
- "list_epic_notes"
|
|
2836
|
+
- "get_epic_note"
|
|
2837
|
+
- "get_commit"
|
|
2838
|
+
- "run_command"
|
|
2839
|
+
- name: "planning_agent"
|
|
2840
|
+
type: AgentComponent
|
|
2841
|
+
prompt_id: "developer_planner"
|
|
2842
|
+
prompt_version: "^1.0.0"
|
|
2843
|
+
model_size_preference: "small"
|
|
2844
|
+
inputs:
|
|
2845
|
+
- from: "context:project_id"
|
|
2846
|
+
as: "project_id"
|
|
2847
|
+
- from: "context:project_http_url_to_repo"
|
|
2848
|
+
as: "repository_url"
|
|
2849
|
+
- from: "context:issue_parser.tool_responses"
|
|
2850
|
+
as: "issue_details"
|
|
2851
|
+
- from: "context:tool_listing_agent.final_answer"
|
|
2852
|
+
as: "coding_agent_tools"
|
|
2853
|
+
- from: "context:inputs.user_rule"
|
|
2854
|
+
as: "agents_dot_md"
|
|
2855
|
+
optional: True
|
|
2856
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2857
|
+
as: "workspace_agent_skills"
|
|
2858
|
+
optional: True
|
|
2859
|
+
toolset:
|
|
2860
|
+
- "list_issues"
|
|
2861
|
+
- "get_issue"
|
|
2862
|
+
- "list_issue_notes"
|
|
2863
|
+
- "get_issue_note"
|
|
2864
|
+
- "get_job_logs"
|
|
2865
|
+
- "get_merge_request"
|
|
2866
|
+
- "get_project"
|
|
2867
|
+
- "get_pipeline_failing_jobs"
|
|
2868
|
+
- "list_all_merge_request_notes"
|
|
2869
|
+
- "list_merge_request_diffs"
|
|
2870
|
+
- "gitlab_issue_search"
|
|
2871
|
+
- "gitlab_merge_request_search"
|
|
2872
|
+
- "read_file"
|
|
2873
|
+
- "find_files"
|
|
2874
|
+
- "list_dir"
|
|
2875
|
+
- "grep"
|
|
2876
|
+
- "get_epic"
|
|
2877
|
+
- "list_epics"
|
|
2878
|
+
- "get_repository_file"
|
|
2879
|
+
- "list_epic_notes"
|
|
2880
|
+
- "get_epic_note"
|
|
2881
|
+
- "get_commit"
|
|
2882
|
+
- "list_commits"
|
|
2883
|
+
- "get_commit_comments"
|
|
2884
|
+
- "get_commit_diff"
|
|
2885
|
+
- "get_work_item"
|
|
2886
|
+
- "list_work_items"
|
|
2887
|
+
- "get_work_item_notes"
|
|
2888
|
+
ui_log_events:
|
|
2889
|
+
- "on_tool_execution_success"
|
|
2890
|
+
- "on_tool_execution_failed"
|
|
2891
|
+
- "on_agent_final_answer"
|
|
2892
|
+
|
|
2893
|
+
- name: "add_plan_comment"
|
|
2894
|
+
type: OneOffComponent
|
|
2895
|
+
prompt_id: "developer_add_plan_comment"
|
|
2896
|
+
prompt_version: "^1.0.0"
|
|
2897
|
+
model_size_preference: "small"
|
|
2898
|
+
inputs:
|
|
2899
|
+
- from: "context:planning_agent.final_answer"
|
|
2900
|
+
as: "plan_text"
|
|
2901
|
+
- from: "context:draft_merge_request_creator.tool_responses"
|
|
2902
|
+
as: "merge_request_details"
|
|
2903
|
+
- from: "context:inputs.user_rule"
|
|
2904
|
+
as: "agents_dot_md"
|
|
2905
|
+
optional: True
|
|
2906
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2907
|
+
as: "workspace_agent_skills"
|
|
2908
|
+
optional: True
|
|
2909
|
+
toolset:
|
|
2910
|
+
- "create_merge_request_note"
|
|
2911
|
+
ui_log_events:
|
|
2912
|
+
- "on_tool_execution_success"
|
|
2913
|
+
- "on_tool_execution_failed"
|
|
2914
|
+
|
|
2915
|
+
- name: "programming_agent"
|
|
2916
|
+
type: AgentComponent
|
|
2917
|
+
prompt_id: "programming_agent"
|
|
2918
|
+
prompt_version: "^1.0.0"
|
|
2919
|
+
model_size_preference: "small"
|
|
2920
|
+
inputs:
|
|
2921
|
+
- from: "context:project_id"
|
|
2922
|
+
as: "project_id"
|
|
2923
|
+
- from: "context:project_http_url_to_repo"
|
|
2924
|
+
as: "repository_url"
|
|
2925
|
+
- from: "context:planning_agent.final_answer"
|
|
2926
|
+
as: "planner_handover"
|
|
2927
|
+
- from: "context:inputs.os_information"
|
|
2928
|
+
as: "os_information"
|
|
2929
|
+
- from: "context:inputs.user_rule"
|
|
2930
|
+
as: "agents_dot_md"
|
|
2931
|
+
optional: True
|
|
2932
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2933
|
+
as: "workspace_agent_skills"
|
|
2934
|
+
optional: True
|
|
2935
|
+
toolset:
|
|
2936
|
+
- "list_issues"
|
|
2937
|
+
- "get_issue"
|
|
2938
|
+
- "list_issue_notes"
|
|
2939
|
+
- "get_issue_note"
|
|
2940
|
+
- "get_job_logs"
|
|
2941
|
+
- "get_merge_request"
|
|
2942
|
+
- "get_pipeline_failing_jobs"
|
|
2943
|
+
- "get_project"
|
|
2944
|
+
- "list_all_merge_request_notes"
|
|
2945
|
+
- "list_merge_request_diffs"
|
|
2946
|
+
- "gitlab_issue_search"
|
|
2947
|
+
- "gitlab_merge_request_search"
|
|
2948
|
+
- "read_file"
|
|
2949
|
+
- "create_file_with_contents"
|
|
2950
|
+
- "edit_file"
|
|
2951
|
+
- "find_files"
|
|
2952
|
+
- "grep"
|
|
2953
|
+
- "mkdir"
|
|
2954
|
+
- "get_epic"
|
|
2955
|
+
- "list_epics"
|
|
2956
|
+
- "get_repository_file"
|
|
2957
|
+
- "list_dir"
|
|
2958
|
+
- "list_epic_notes"
|
|
2959
|
+
- "get_epic_note"
|
|
2960
|
+
- "get_commit"
|
|
2961
|
+
- "run_command"
|
|
2962
|
+
ui_log_events:
|
|
2963
|
+
- "on_tool_execution_success"
|
|
2964
|
+
- "on_tool_execution_failed"
|
|
2965
|
+
- "on_agent_final_answer"
|
|
2966
|
+
- name: "git_actions"
|
|
2967
|
+
type: OneOffComponent
|
|
2968
|
+
prompt_id: "push_to_remote"
|
|
2969
|
+
prompt_version: "^1.0.0"
|
|
2970
|
+
model_size_preference: "small"
|
|
2971
|
+
inputs:
|
|
2972
|
+
- from: "context:project_http_url_to_repo"
|
|
2973
|
+
as: "repository_url"
|
|
2974
|
+
- from: "context:programming_agent.final_answer"
|
|
2975
|
+
as: "programming_agent_handover"
|
|
2976
|
+
- from: "context:draft_merge_request_creator.tool_responses"
|
|
2977
|
+
as: "merge_request_details"
|
|
2978
|
+
- from: "context:inputs.agent_platform_standard_context.session_owner_id"
|
|
2979
|
+
as: "assignee_id"
|
|
2980
|
+
- from: "context:goal"
|
|
2981
|
+
as: "issue_url"
|
|
2982
|
+
- from: "context:inputs.user_rule"
|
|
2983
|
+
as: "agents_dot_md"
|
|
2984
|
+
optional: True
|
|
2985
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
2986
|
+
as: "workspace_agent_skills"
|
|
2987
|
+
optional: True
|
|
2988
|
+
toolset:
|
|
2989
|
+
- "run_git_command"
|
|
2990
|
+
- "update_merge_request"
|
|
2991
|
+
ui_log_events:
|
|
2992
|
+
- "on_tool_execution_success"
|
|
2993
|
+
- "on_tool_execution_failed"
|
|
2994
|
+
|
|
2995
|
+
routers:
|
|
2996
|
+
- from: "issue_parser"
|
|
2997
|
+
to: "create_repository_branch"
|
|
2998
|
+
- from: "create_repository_branch"
|
|
2999
|
+
to: "draft_merge_request_creator"
|
|
3000
|
+
- from: "draft_merge_request_creator"
|
|
3001
|
+
to: "tool_listing_agent"
|
|
3002
|
+
- from: "tool_listing_agent"
|
|
3003
|
+
to: "planning_agent"
|
|
3004
|
+
- from: "planning_agent"
|
|
3005
|
+
to: "add_plan_comment"
|
|
3006
|
+
- from: "add_plan_comment"
|
|
3007
|
+
to: "programming_agent"
|
|
3008
|
+
- from: "programming_agent"
|
|
3009
|
+
to: "git_actions"
|
|
3010
|
+
- from: "git_actions"
|
|
3011
|
+
condition:
|
|
3012
|
+
input: "context:git_actions.execution_result"
|
|
3013
|
+
routes:
|
|
3014
|
+
"success": "end"
|
|
3015
|
+
"failed": "abort"
|
|
3016
|
+
|
|
3017
|
+
flow:
|
|
3018
|
+
entry_point: "issue_parser"
|
|
3019
|
+
inputs:
|
|
3020
|
+
- category: agent_platform_standard_context
|
|
3021
|
+
input_schema:
|
|
3022
|
+
workload_branch:
|
|
3023
|
+
type: string
|
|
3024
|
+
description: The workload branch for the GitLab Merge Request
|
|
3025
|
+
primary_branch:
|
|
3026
|
+
type: string
|
|
3027
|
+
description: Merge Request target branch
|
|
3028
|
+
session_owner_id:
|
|
3029
|
+
type: string
|
|
3030
|
+
description: Human user's ID that initiated the flow
|
|
3031
|
+
`,
|
|
3032
|
+
"duo_permissions_assistant": `version: "v1"
|
|
3033
|
+
environment: chat-partial
|
|
3034
|
+
components:
|
|
3035
|
+
- name: "duo_permissions_assistant"
|
|
3036
|
+
type: AgentComponent
|
|
3037
|
+
prompt_id: "duo_permissions_assistant_prompt"
|
|
3038
|
+
inputs:
|
|
3039
|
+
- from: "context:goal"
|
|
3040
|
+
as: "goal"
|
|
3041
|
+
toolset:
|
|
3042
|
+
- "gitlab_graphql"
|
|
3043
|
+
- "update_form_permissions"
|
|
3044
|
+
ui_log_events:
|
|
3045
|
+
- "on_agent_final_answer"
|
|
3046
|
+
- "on_tool_execution_success"
|
|
3047
|
+
routers: []
|
|
3048
|
+
flow:
|
|
3049
|
+
entry_point: "duo_permissions_assistant"
|
|
3050
|
+
prompts:
|
|
3051
|
+
- name: "duo_permissions_assistant_prompt"
|
|
3052
|
+
prompt_id: "duo_permissions_assistant_prompt"
|
|
3053
|
+
model:
|
|
3054
|
+
params:
|
|
3055
|
+
max_tokens: 8192
|
|
3056
|
+
unit_primitives:
|
|
3057
|
+
- duo_agent_platform
|
|
3058
|
+
prompt_template:
|
|
3059
|
+
system: |
|
|
3060
|
+
You are a GitLab fine-grained access token permissions assistant.
|
|
3061
|
+
You help users pick the right permissions for their access tokens.
|
|
3062
|
+
|
|
3063
|
+
STEP 1 \u2014 FETCH PERMISSIONS:
|
|
3064
|
+
On the first message, call the gitlab_graphql tool with this query to get all available permissions:
|
|
3065
|
+
query GetAccessTokenPermissions { accessTokenPermissions { name description } }
|
|
3066
|
+
|
|
3067
|
+
STEP 2 \u2014 SUGGEST PERMISSIONS:
|
|
3068
|
+
Using ONLY permission names returned by the tool:
|
|
3069
|
+
- 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.
|
|
3070
|
+
- Apply the principle of least privilege.
|
|
3071
|
+
- Do not suggest legacy scopes (api, read_api, write_repository, etc.) or anything not in the tool results.
|
|
3072
|
+
- If nothing matches, say: "No matching permissions are available."
|
|
3073
|
+
- 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.
|
|
3074
|
+
- If the user's request is vague, make your best guess and explain your reasoning. The user can refine afterward.
|
|
3075
|
+
|
|
3076
|
+
STEP 3 \u2014 APPLY CHANGES:
|
|
3077
|
+
After explaining your reasoning, call the update_form_permissions tool to apply the changes.
|
|
3078
|
+
Only include "select" and/or "clear" as needed.
|
|
3079
|
+
user: |
|
|
3080
|
+
{{goal}}
|
|
3081
|
+
placeholder: history
|
|
3082
|
+
`,
|
|
3083
|
+
"fix_pipeline": `name: "Fix pipeline"
|
|
3084
|
+
description: |
|
|
3085
|
+
Fix pipeline flow can be run for a failed CI jobs.
|
|
3086
|
+
It opens a new MR with changes that address root cause of CI job failures.
|
|
3087
|
+
product_group: agent_foundations
|
|
3088
|
+
version: "v1"
|
|
3089
|
+
environment: ambient
|
|
3090
|
+
|
|
3091
|
+
components:
|
|
3092
|
+
- name: "fix_pipeline_context"
|
|
3093
|
+
type: AgentComponent
|
|
3094
|
+
prompt_id: "fix_pipeline_context"
|
|
3095
|
+
prompt_version: "^1.0.0"
|
|
3096
|
+
inputs:
|
|
3097
|
+
- from: "context:goal"
|
|
3098
|
+
as: "pipeline_url"
|
|
3099
|
+
- from: "context:inputs.merge_request.url"
|
|
3100
|
+
as: "merge_request_url"
|
|
3101
|
+
- from: "context:inputs.user_rule"
|
|
3102
|
+
as: "agents_dot_md"
|
|
3103
|
+
optional: True
|
|
3104
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3105
|
+
as: "workspace_agent_skills"
|
|
3106
|
+
optional: True
|
|
3107
|
+
toolset:
|
|
3108
|
+
- get_downstream_pipelines
|
|
3109
|
+
- get_pipeline_failing_jobs
|
|
3110
|
+
- get_job_logs
|
|
3111
|
+
- list_merge_request_diffs
|
|
3112
|
+
- read_file
|
|
3113
|
+
- find_files
|
|
3114
|
+
- list_dir
|
|
3115
|
+
ui_log_events:
|
|
3116
|
+
- "on_agent_final_answer"
|
|
3117
|
+
- "on_tool_execution_success"
|
|
3118
|
+
- "on_tool_execution_failed"
|
|
3119
|
+
|
|
3120
|
+
- name: "fix_pipeline_decide_approach"
|
|
3121
|
+
type: AgentComponent
|
|
3122
|
+
prompt_id: "fix_pipeline_decide_approach"
|
|
3123
|
+
prompt_version: "^1.0.0"
|
|
3124
|
+
response_schema_id: "fix_pipeline_decide_approach"
|
|
3125
|
+
response_schema_version: "^1.0.0"
|
|
3126
|
+
inputs:
|
|
3127
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3128
|
+
as: "fix_pipeline_context_results"
|
|
3129
|
+
toolset: []
|
|
3130
|
+
ui_log_events:
|
|
3131
|
+
- "on_agent_final_answer"
|
|
3132
|
+
|
|
3133
|
+
- name: "fix_pipeline_add_comment"
|
|
3134
|
+
type: AgentComponent
|
|
3135
|
+
prompt_id: "fix_pipeline_add_comment"
|
|
3136
|
+
prompt_version: "^1.0.0"
|
|
3137
|
+
inputs:
|
|
3138
|
+
- from: "context:goal"
|
|
3139
|
+
as: "pipeline_url"
|
|
3140
|
+
- from: "context:inputs.merge_request.url"
|
|
3141
|
+
as: "merge_request_url"
|
|
3142
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3143
|
+
as: "fix_pipeline_context_results"
|
|
3144
|
+
- from: "context:inputs.user_rule"
|
|
3145
|
+
as: "agents_dot_md"
|
|
3146
|
+
optional: True
|
|
3147
|
+
- from: "context:workflow_id"
|
|
3148
|
+
as: "workflow_id"
|
|
3149
|
+
- from: "context:session_url"
|
|
3150
|
+
as: "session_url"
|
|
3151
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3152
|
+
as: "workspace_agent_skills"
|
|
3153
|
+
optional: True
|
|
3154
|
+
toolset: ["create_merge_request_note"]
|
|
3155
|
+
ui_log_events:
|
|
3156
|
+
- "on_agent_final_answer"
|
|
3157
|
+
- "on_tool_execution_success"
|
|
3158
|
+
- "on_tool_execution_failed"
|
|
3159
|
+
|
|
3160
|
+
- name: "create_repository_branch"
|
|
3161
|
+
type: AgentComponent
|
|
3162
|
+
prompt_id: "create_repository_branch"
|
|
3163
|
+
prompt_version: "^1.0.0"
|
|
3164
|
+
toolset:
|
|
3165
|
+
- "run_git_command"
|
|
3166
|
+
inputs:
|
|
3167
|
+
- from: "context:project_id"
|
|
3168
|
+
as: "project_id"
|
|
3169
|
+
- from: "context:inputs.pipeline.source_branch"
|
|
3170
|
+
as: "ref"
|
|
3171
|
+
- from: "context:project_http_url_to_repo"
|
|
3172
|
+
as: "repository_url"
|
|
3173
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3174
|
+
as: "naming_context"
|
|
3175
|
+
- from: "context:workflow_id"
|
|
3176
|
+
as: "workflow_id"
|
|
3177
|
+
- from: "context:inputs.user_rule"
|
|
3178
|
+
as: "agents_dot_md"
|
|
3179
|
+
optional: True
|
|
3180
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3181
|
+
as: "workspace_agent_skills"
|
|
3182
|
+
optional: True
|
|
3183
|
+
ui_log_events:
|
|
3184
|
+
- "on_tool_execution_success"
|
|
3185
|
+
- "on_tool_execution_failed"
|
|
3186
|
+
|
|
3187
|
+
- name: "fix_pipeline_create_plan"
|
|
3188
|
+
type: AgentComponent
|
|
3189
|
+
prompt_id: "fix_pipeline_create_plan"
|
|
3190
|
+
prompt_version: "^1.0.0"
|
|
3191
|
+
inputs:
|
|
3192
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3193
|
+
as: "fix_pipeline_context_results"
|
|
3194
|
+
- from: "context:inputs.pipeline.source_branch"
|
|
3195
|
+
as: "source_branch"
|
|
3196
|
+
- from: "context:inputs.user_rule"
|
|
3197
|
+
as: "agents_dot_md"
|
|
3198
|
+
optional: True
|
|
3199
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3200
|
+
as: "workspace_agent_skills"
|
|
3201
|
+
optional: True
|
|
3202
|
+
toolset: []
|
|
3203
|
+
ui_log_events:
|
|
3204
|
+
- "on_agent_final_answer"
|
|
3205
|
+
|
|
3206
|
+
- name: "fix_pipeline_execution"
|
|
3207
|
+
type: AgentComponent
|
|
3208
|
+
prompt_id: "fix_pipeline_execution"
|
|
3209
|
+
prompt_version: "^1.0.0"
|
|
3210
|
+
inputs:
|
|
3211
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3212
|
+
as: "fix_pipeline_context_results"
|
|
3213
|
+
- from: "context:fix_pipeline_create_plan.final_answer"
|
|
3214
|
+
as: "fix_pipeline_plan"
|
|
3215
|
+
- from: "context:inputs.user_rule"
|
|
3216
|
+
as: "agents_dot_md"
|
|
3217
|
+
optional: True
|
|
3218
|
+
- from: "context:inputs.os_information"
|
|
3219
|
+
as: "os_information"
|
|
3220
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3221
|
+
as: "workspace_agent_skills"
|
|
3222
|
+
optional: True
|
|
3223
|
+
toolset:
|
|
3224
|
+
- create_file_with_contents
|
|
3225
|
+
- edit_file
|
|
3226
|
+
- find_files
|
|
3227
|
+
- mkdir
|
|
3228
|
+
- read_file
|
|
3229
|
+
- run_command
|
|
3230
|
+
ui_log_events:
|
|
3231
|
+
- "on_agent_final_answer"
|
|
3232
|
+
- "on_tool_execution_success"
|
|
3233
|
+
- "on_tool_execution_failed"
|
|
3234
|
+
|
|
3235
|
+
- name: "fix_pipeline_git_commit"
|
|
3236
|
+
type: AgentComponent
|
|
3237
|
+
prompt_id: "commit_changes"
|
|
3238
|
+
prompt_version: "^1.0.0"
|
|
3239
|
+
inputs:
|
|
3240
|
+
- from: "context:project_http_url_to_repo"
|
|
3241
|
+
as: "repository_url"
|
|
3242
|
+
- from: "context:inputs.user_rule"
|
|
3243
|
+
as: "agents_dot_md"
|
|
3244
|
+
optional: True
|
|
3245
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3246
|
+
as: "workspace_agent_skills"
|
|
3247
|
+
optional: True
|
|
3248
|
+
toolset:
|
|
3249
|
+
- "run_git_command"
|
|
3250
|
+
ui_log_events:
|
|
3251
|
+
- "on_tool_execution_success"
|
|
3252
|
+
- "on_tool_execution_failed"
|
|
3253
|
+
|
|
3254
|
+
- name: "fix_pipeline_git_push"
|
|
3255
|
+
type: AgentComponent
|
|
3256
|
+
prompt_id: "fix_pipeline_push_changes"
|
|
3257
|
+
prompt_version: "^1.0.0"
|
|
3258
|
+
inputs:
|
|
3259
|
+
- from: "context:goal"
|
|
3260
|
+
as: "pipeline_url"
|
|
3261
|
+
- from: "context:project_http_url_to_repo"
|
|
3262
|
+
as: "repository_url"
|
|
3263
|
+
- from: "context:inputs.merge_request.url"
|
|
3264
|
+
as: "merge_request_url"
|
|
3265
|
+
- from: "context:inputs.pipeline.source_branch"
|
|
3266
|
+
as: "source_branch"
|
|
3267
|
+
- from: "context:inputs.user_rule"
|
|
3268
|
+
as: "agents_dot_md"
|
|
3269
|
+
optional: True
|
|
3270
|
+
- from: "context:workflow_id"
|
|
3271
|
+
as: "workflow_id"
|
|
3272
|
+
- from: "context:session_url"
|
|
3273
|
+
as: "session_url"
|
|
3274
|
+
- from: "context:project_id"
|
|
3275
|
+
as: "project_id"
|
|
3276
|
+
- from: "context:create_repository_branch.final_answer"
|
|
3277
|
+
as: "target_branch_details"
|
|
3278
|
+
- from: "context:inputs.agent_platform_standard_context.primary_branch"
|
|
3279
|
+
as: "default_branch"
|
|
3280
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3281
|
+
as: "workspace_agent_skills"
|
|
3282
|
+
optional: True
|
|
3283
|
+
toolset:
|
|
3284
|
+
- "run_git_command"
|
|
3285
|
+
- "create_merge_request"
|
|
3286
|
+
ui_log_events:
|
|
3287
|
+
- "on_agent_final_answer"
|
|
3288
|
+
- "on_tool_execution_success"
|
|
3289
|
+
- "on_tool_execution_failed"
|
|
3290
|
+
|
|
3291
|
+
- name: "fix_pipeline_summarize"
|
|
3292
|
+
type: OneOffComponent
|
|
3293
|
+
prompt_id: "fix_pipeline_summarize_changes"
|
|
3294
|
+
prompt_version: "^1.0.0"
|
|
3295
|
+
inputs:
|
|
3296
|
+
- from: "context:goal"
|
|
3297
|
+
as: "pipeline_url"
|
|
3298
|
+
- from: "context:inputs.merge_request.url"
|
|
3299
|
+
as: "merge_request_url"
|
|
3300
|
+
- from: "context:inputs.user_rule"
|
|
3301
|
+
as: "agents_dot_md"
|
|
3302
|
+
optional: True
|
|
3303
|
+
- from: "context:fix_pipeline_git_push.final_answer"
|
|
3304
|
+
as: "git_push_response"
|
|
3305
|
+
- from: "context:workflow_id"
|
|
3306
|
+
as: "workflow_id"
|
|
3307
|
+
- from: "context:session_url"
|
|
3308
|
+
as: "session_url"
|
|
3309
|
+
- from: "context:fix_pipeline_execution.final_answer"
|
|
3310
|
+
as: "execution_results"
|
|
3311
|
+
- from: "context:inputs.agent_platform_standard_context.session_owner_id"
|
|
3312
|
+
as: "assignee_id"
|
|
3313
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3314
|
+
as: "workspace_agent_skills"
|
|
3315
|
+
optional: True
|
|
3316
|
+
toolset:
|
|
3317
|
+
- "update_merge_request"
|
|
3318
|
+
ui_log_events:
|
|
3319
|
+
- "on_agent_final_answer"
|
|
3320
|
+
- "on_tool_execution_success"
|
|
3321
|
+
- "on_tool_execution_failed"
|
|
3322
|
+
|
|
3323
|
+
- name: "fix_pipeline_decide_comment"
|
|
3324
|
+
type: AgentComponent
|
|
3325
|
+
prompt_id: "fix_pipeline_decide_comment"
|
|
3326
|
+
prompt_version: "^1.0.0"
|
|
3327
|
+
response_schema_id: "fix_pipeline_decide_comment"
|
|
3328
|
+
response_schema_version: "^1.0.0"
|
|
3329
|
+
inputs:
|
|
3330
|
+
- from: "context:inputs.merge_request.url"
|
|
3331
|
+
as: "merge_request_url"
|
|
3332
|
+
- from: "context:inputs.user_rule"
|
|
3333
|
+
as: "agents_dot_md"
|
|
3334
|
+
optional: True
|
|
3335
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3336
|
+
as: "workspace_agent_skills"
|
|
3337
|
+
optional: True
|
|
3338
|
+
toolset: []
|
|
3339
|
+
ui_log_events:
|
|
3340
|
+
- "on_agent_final_answer"
|
|
3341
|
+
|
|
3342
|
+
- name: "fix_pipeline_comment_link"
|
|
3343
|
+
type: OneOffComponent
|
|
3344
|
+
prompt_id: "fix_pipeline_comment_link"
|
|
3345
|
+
prompt_version: "^1.0.0"
|
|
3346
|
+
max_correction_attempts: 3
|
|
3347
|
+
inputs:
|
|
3348
|
+
- from: "context:inputs.merge_request.url"
|
|
3349
|
+
as: "merge_request_url"
|
|
3350
|
+
- from: "context:fix_pipeline_git_push.final_answer"
|
|
3351
|
+
as: "git_push_response"
|
|
3352
|
+
- from: "context:inputs.user_rule"
|
|
3353
|
+
as: "agents_dot_md"
|
|
3354
|
+
optional: True
|
|
3355
|
+
- from: "context:workflow_id"
|
|
3356
|
+
as: "workflow_id"
|
|
3357
|
+
- from: "context:session_url"
|
|
3358
|
+
as: "session_url"
|
|
3359
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3360
|
+
as: "workspace_agent_skills"
|
|
3361
|
+
optional: True
|
|
3362
|
+
toolset: ["create_merge_request_note"]
|
|
3363
|
+
ui_log_events:
|
|
3364
|
+
- "on_tool_execution_success"
|
|
3365
|
+
- "on_tool_execution_failed"
|
|
3366
|
+
|
|
3367
|
+
routers:
|
|
3368
|
+
- from: "fix_pipeline_context"
|
|
3369
|
+
to: "fix_pipeline_decide_approach"
|
|
3370
|
+
- from: "fix_pipeline_decide_approach"
|
|
3371
|
+
condition:
|
|
3372
|
+
input: "context:fix_pipeline_decide_approach.final_answer.decision"
|
|
3373
|
+
routes:
|
|
3374
|
+
"add_comment": "fix_pipeline_add_comment"
|
|
3375
|
+
"create_fix": "create_repository_branch"
|
|
3376
|
+
"default_route": "fix_pipeline_add_comment"
|
|
3377
|
+
- from: "fix_pipeline_add_comment"
|
|
3378
|
+
to: "end"
|
|
3379
|
+
- from: "create_repository_branch"
|
|
3380
|
+
to: "fix_pipeline_create_plan"
|
|
3381
|
+
- from: "fix_pipeline_create_plan"
|
|
3382
|
+
to: "fix_pipeline_execution"
|
|
3383
|
+
- from: "fix_pipeline_execution"
|
|
3384
|
+
to: "fix_pipeline_git_commit"
|
|
3385
|
+
- from: "fix_pipeline_git_commit"
|
|
3386
|
+
to: "fix_pipeline_git_push"
|
|
3387
|
+
- from: "fix_pipeline_git_push"
|
|
3388
|
+
to: "fix_pipeline_summarize"
|
|
3389
|
+
- from: "fix_pipeline_summarize"
|
|
3390
|
+
to: "fix_pipeline_decide_comment"
|
|
3391
|
+
- from: "fix_pipeline_decide_comment"
|
|
3392
|
+
condition:
|
|
3393
|
+
input: "context:fix_pipeline_decide_comment.final_answer.decision"
|
|
3394
|
+
routes:
|
|
3395
|
+
"comment_link": "fix_pipeline_comment_link"
|
|
3396
|
+
"end": "end"
|
|
3397
|
+
"default_route": "end"
|
|
3398
|
+
- from: "fix_pipeline_comment_link"
|
|
3399
|
+
to: "end"
|
|
3400
|
+
|
|
3401
|
+
flow:
|
|
3402
|
+
entry_point: "fix_pipeline_context"
|
|
3403
|
+
inputs:
|
|
3404
|
+
- category: merge_request
|
|
3405
|
+
input_schema:
|
|
3406
|
+
url:
|
|
3407
|
+
type: string
|
|
3408
|
+
format: uri
|
|
3409
|
+
description: The URL for the GitLab Merge Request
|
|
3410
|
+
- category: pipeline
|
|
3411
|
+
input_schema:
|
|
3412
|
+
source_branch:
|
|
3413
|
+
type: string
|
|
3414
|
+
- category: agent_platform_standard_context
|
|
3415
|
+
input_schema:
|
|
3416
|
+
workload_branch:
|
|
3417
|
+
type: string
|
|
3418
|
+
description: The workload branch for the GitLab Merge Request
|
|
3419
|
+
primary_branch:
|
|
3420
|
+
type: string
|
|
3421
|
+
description: Merge Request target branch
|
|
3422
|
+
session_owner_id:
|
|
3423
|
+
type: string
|
|
3424
|
+
description: Human user's ID that initiated the flow
|
|
3425
|
+
`,
|
|
3426
|
+
"fix_pipeline_next": `name: "Fix pipeline (next)"
|
|
3427
|
+
description: |
|
|
3428
|
+
Fix pipeline flow can be run for a failed CI jobs.
|
|
3429
|
+
When a merge request URL is provided, it pushes the fix directly to that MR's branch.
|
|
3430
|
+
Otherwise it opens a new MR with changes that address root cause of CI job failures.
|
|
3431
|
+
product_group: agent_foundations
|
|
3432
|
+
version: "v1"
|
|
3433
|
+
environment: ambient
|
|
3434
|
+
|
|
3435
|
+
components:
|
|
3436
|
+
- name: "fix_pipeline_context"
|
|
3437
|
+
type: AgentComponent
|
|
3438
|
+
prompt_id: "fix_pipeline_context"
|
|
3439
|
+
prompt_version: "^1.0.0"
|
|
3440
|
+
inputs:
|
|
3441
|
+
- from: "context:goal"
|
|
3442
|
+
as: "pipeline_url"
|
|
3443
|
+
- from: "context:inputs.merge_request.url"
|
|
3444
|
+
as: "merge_request_url"
|
|
3445
|
+
- from: "context:inputs.user_rule"
|
|
3446
|
+
as: "agents_dot_md"
|
|
3447
|
+
optional: True
|
|
3448
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3449
|
+
as: "workspace_agent_skills"
|
|
3450
|
+
optional: True
|
|
3451
|
+
toolset:
|
|
3452
|
+
- get_downstream_pipelines
|
|
3453
|
+
- get_pipeline_failing_jobs
|
|
3454
|
+
- get_job_logs
|
|
3455
|
+
- list_merge_request_diffs
|
|
3456
|
+
- read_file
|
|
3457
|
+
- find_files
|
|
3458
|
+
- list_dir
|
|
3459
|
+
ui_log_events:
|
|
3460
|
+
- "on_agent_final_answer"
|
|
3461
|
+
- "on_tool_execution_success"
|
|
3462
|
+
- "on_tool_execution_failed"
|
|
3463
|
+
|
|
3464
|
+
- name: "fix_pipeline_next_decide_approach"
|
|
3465
|
+
type: AgentComponent
|
|
3466
|
+
prompt_id: "fix_pipeline_next_decide_approach"
|
|
3467
|
+
prompt_version: "^1.0.0"
|
|
3468
|
+
response_schema_id: "fix_pipeline_next_decide_approach"
|
|
3469
|
+
response_schema_version: "^1.0.0"
|
|
3470
|
+
inputs:
|
|
3471
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3472
|
+
as: "fix_pipeline_context_results"
|
|
3473
|
+
- from: "context:inputs.merge_request.url"
|
|
3474
|
+
as: "merge_request_url"
|
|
3475
|
+
optional: True
|
|
3476
|
+
- from: "context:inputs.user_rule"
|
|
3477
|
+
as: "agents_dot_md"
|
|
3478
|
+
optional: True
|
|
3479
|
+
toolset: []
|
|
3480
|
+
ui_log_events:
|
|
3481
|
+
- "on_agent_final_answer"
|
|
3482
|
+
|
|
3483
|
+
- name: "fix_pipeline_add_comment"
|
|
3484
|
+
type: AgentComponent
|
|
3485
|
+
prompt_id: "fix_pipeline_add_comment"
|
|
3486
|
+
prompt_version: "^1.0.0"
|
|
3487
|
+
inputs:
|
|
3488
|
+
- from: "context:goal"
|
|
3489
|
+
as: "pipeline_url"
|
|
3490
|
+
- from: "context:inputs.merge_request.url"
|
|
3491
|
+
as: "merge_request_url"
|
|
3492
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3493
|
+
as: "fix_pipeline_context_results"
|
|
3494
|
+
- from: "context:inputs.user_rule"
|
|
3495
|
+
as: "agents_dot_md"
|
|
3496
|
+
optional: True
|
|
3497
|
+
- from: "context:workflow_id"
|
|
3498
|
+
as: "workflow_id"
|
|
3499
|
+
- from: "context:session_url"
|
|
3500
|
+
as: "session_url"
|
|
3501
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3502
|
+
as: "workspace_agent_skills"
|
|
3503
|
+
optional: True
|
|
3504
|
+
toolset: ["create_merge_request_note"]
|
|
3505
|
+
ui_log_events:
|
|
3506
|
+
- "on_agent_final_answer"
|
|
3507
|
+
- "on_tool_execution_success"
|
|
3508
|
+
- "on_tool_execution_failed"
|
|
3509
|
+
|
|
3510
|
+
- name: "fix_pipeline_next_checkout_existing_branch"
|
|
3511
|
+
type: AgentComponent
|
|
3512
|
+
prompt_id: "fix_pipeline_next_checkout_existing_branch"
|
|
3513
|
+
prompt_version: "^1.0.0"
|
|
3514
|
+
inputs:
|
|
3515
|
+
- from: "context:inputs.pipeline.source_branch"
|
|
3516
|
+
as: "ref"
|
|
3517
|
+
- from: "context:project_http_url_to_repo"
|
|
3518
|
+
as: "repository_url"
|
|
3519
|
+
- from: "context:inputs.user_rule"
|
|
3520
|
+
as: "agents_dot_md"
|
|
3521
|
+
optional: True
|
|
3522
|
+
toolset:
|
|
3523
|
+
- "run_git_command"
|
|
3524
|
+
ui_log_events:
|
|
3525
|
+
- "on_tool_execution_success"
|
|
3526
|
+
- "on_tool_execution_failed"
|
|
3527
|
+
|
|
3528
|
+
- name: "create_repository_branch"
|
|
3529
|
+
type: AgentComponent
|
|
3530
|
+
prompt_id: "create_repository_branch"
|
|
3531
|
+
prompt_version: "^1.0.0"
|
|
3532
|
+
toolset:
|
|
3533
|
+
- "run_git_command"
|
|
3534
|
+
inputs:
|
|
3535
|
+
- from: "context:project_id"
|
|
3536
|
+
as: "project_id"
|
|
3537
|
+
- from: "context:inputs.pipeline.source_branch"
|
|
3538
|
+
as: "ref"
|
|
3539
|
+
- from: "context:project_http_url_to_repo"
|
|
3540
|
+
as: "repository_url"
|
|
3541
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3542
|
+
as: "naming_context"
|
|
3543
|
+
- from: "context:workflow_id"
|
|
3544
|
+
as: "workflow_id"
|
|
3545
|
+
- from: "context:inputs.user_rule"
|
|
3546
|
+
as: "agents_dot_md"
|
|
3547
|
+
optional: True
|
|
3548
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3549
|
+
as: "workspace_agent_skills"
|
|
3550
|
+
optional: True
|
|
3551
|
+
ui_log_events:
|
|
3552
|
+
- "on_tool_execution_success"
|
|
3553
|
+
- "on_tool_execution_failed"
|
|
3554
|
+
|
|
3555
|
+
- name: "fix_pipeline_create_plan"
|
|
3556
|
+
type: AgentComponent
|
|
3557
|
+
prompt_id: "fix_pipeline_create_plan"
|
|
3558
|
+
prompt_version: "^1.0.0"
|
|
3559
|
+
inputs:
|
|
3560
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3561
|
+
as: "fix_pipeline_context_results"
|
|
3562
|
+
- from: "context:inputs.pipeline.source_branch"
|
|
3563
|
+
as: "source_branch"
|
|
3564
|
+
- from: "context:inputs.user_rule"
|
|
3565
|
+
as: "agents_dot_md"
|
|
3566
|
+
optional: True
|
|
3567
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3568
|
+
as: "workspace_agent_skills"
|
|
3569
|
+
optional: True
|
|
3570
|
+
toolset: []
|
|
3571
|
+
ui_log_events:
|
|
3572
|
+
- "on_agent_final_answer"
|
|
3573
|
+
|
|
3574
|
+
- name: "fix_pipeline_execution"
|
|
3575
|
+
type: AgentComponent
|
|
3576
|
+
prompt_id: "fix_pipeline_execution"
|
|
3577
|
+
prompt_version: "^1.0.0"
|
|
3578
|
+
inputs:
|
|
3579
|
+
- from: "context:fix_pipeline_context.final_answer"
|
|
3580
|
+
as: "fix_pipeline_context_results"
|
|
3581
|
+
- from: "context:fix_pipeline_create_plan.final_answer"
|
|
3582
|
+
as: "fix_pipeline_plan"
|
|
3583
|
+
- from: "context:inputs.user_rule"
|
|
3584
|
+
as: "agents_dot_md"
|
|
3585
|
+
optional: True
|
|
3586
|
+
- from: "context:inputs.os_information"
|
|
3587
|
+
as: "os_information"
|
|
3588
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3589
|
+
as: "workspace_agent_skills"
|
|
3590
|
+
optional: True
|
|
3591
|
+
toolset:
|
|
3592
|
+
- create_file_with_contents
|
|
3593
|
+
- edit_file
|
|
3594
|
+
- find_files
|
|
3595
|
+
- mkdir
|
|
3596
|
+
- read_file
|
|
3597
|
+
- run_command
|
|
3598
|
+
ui_log_events:
|
|
3599
|
+
- "on_agent_final_answer"
|
|
3600
|
+
- "on_tool_execution_success"
|
|
3601
|
+
- "on_tool_execution_failed"
|
|
3602
|
+
|
|
3603
|
+
- name: "fix_pipeline_git_commit"
|
|
3604
|
+
type: AgentComponent
|
|
3605
|
+
prompt_id: "commit_changes"
|
|
3606
|
+
prompt_version: "^1.0.0"
|
|
3607
|
+
inputs:
|
|
3608
|
+
- from: "context:project_http_url_to_repo"
|
|
3609
|
+
as: "repository_url"
|
|
3610
|
+
- from: "context:inputs.user_rule"
|
|
3611
|
+
as: "agents_dot_md"
|
|
3612
|
+
optional: True
|
|
3613
|
+
- from: "context:inputs.workspace_agent_skills"
|
|
3614
|
+
as: "workspace_agent_skills"
|
|
3615
|
+
optional: True
|
|
3616
|
+
toolset:
|
|
3617
|
+
- "run_git_command"
|
|
3618
|
+
ui_log_events:
|
|
3619
|
+
- "on_tool_execution_success"
|
|
3620
|
+
- "on_tool_execution_failed"
|
|
3621
|
+
|
|
3622
|
+
- name: "fix_pipeline_git_push"
|
|
3623
|
+
type: AgentComponent
|
|
3624
|
+
prompt_id: "fix_pipeline_next_push_changes"
|
|
3625
|
+
prompt_version: "^1.0.0"
|
|
3626
|
+
inputs:
|
|
3627
|
+
- from: "context:project_http_url_to_repo"
|
|
3628
|
+
as: "repository_url"
|
|
3629
|
+
- from: "context:inputs.user_rule"
|
|
3630
|
+
as: "agents_dot_md"
|
|
3631
|
+
optional: True
|
|
3632
|
+
toolset:
|
|
3633
|
+
- "run_git_command"
|
|
3634
|
+
ui_log_events:
|
|
3635
|
+
- "on_agent_final_answer"
|
|
3636
|
+
- "on_tool_execution_success"
|
|
3637
|
+
- "on_tool_execution_failed"
|
|
3638
|
+
|
|
3639
|
+
- name: "fix_pipeline_next_create_new_mr"
|
|
3640
|
+
type: AgentComponent
|
|
3641
|
+
prompt_id: "fix_pipeline_next_create_new_mr"
|
|
3642
|
+
prompt_version: "^1.0.0"
|
|
3643
|
+
inputs:
|
|
3644
|
+
- from: "context:goal"
|
|
3645
|
+
as: "pipeline_url"
|
|
3646
|
+
- from: "context:project_id"
|
|
3647
|
+
as: "project_id"
|
|
3648
|
+
- from: "context:inputs.pipeline.source_branch"
|
|
3649
|
+
as: "source_branch"
|
|
3650
|
+
- from: "context:inputs.agent_platform_standard_context.primary_branch"
|
|
3651
|
+
as: "default_branch"
|
|
3652
|
+
- from: "context:fix_pipeline_git_push.final_answer"
|
|
3653
|
+
as: "git_push_response"
|
|
3654
|
+
- from: "context:fix_pipeline_execution.final_answer"
|
|
3655
|
+
as: "execution_results"
|
|
3656
|
+
- from: "context:workflow_id"
|
|
3657
|
+
as: "workflow_id"
|
|
3658
|
+
- from: "context:session_url"
|
|
3659
|
+
as: "session_url"
|
|
3660
|
+
- from: "context:inputs.agent_platform_standard_context.session_owner_id"
|
|
3661
|
+
as: "assignee_id"
|
|
3662
|
+
- from: "context:inputs.user_rule"
|
|
3663
|
+
as: "agents_dot_md"
|
|
3664
|
+
optional: True
|
|
3665
|
+
toolset:
|
|
3666
|
+
- "create_merge_request"
|
|
3667
|
+
- "update_merge_request"
|
|
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_comment_existing_mr"
|
|
3674
|
+
type: OneOffComponent
|
|
3675
|
+
prompt_id: "fix_pipeline_next_comment_existing_mr"
|
|
3676
|
+
prompt_version: "^1.0.0"
|
|
3677
|
+
inputs:
|
|
3678
|
+
- from: "context:inputs.merge_request.url"
|
|
3679
|
+
as: "merge_request_url"
|
|
3680
|
+
- from: "context:fix_pipeline_git_push.final_answer"
|
|
3681
|
+
as: "git_push_response"
|
|
3682
|
+
- from: "context:fix_pipeline_execution.final_answer"
|
|
3683
|
+
as: "execution_results"
|
|
3684
|
+
- from: "context:workflow_id"
|
|
3685
|
+
as: "workflow_id"
|
|
3686
|
+
- from: "context:session_url"
|
|
3687
|
+
as: "session_url"
|
|
3688
|
+
- from: "context:inputs.user_rule"
|
|
3689
|
+
as: "agents_dot_md"
|
|
3690
|
+
optional: True
|
|
3691
|
+
toolset:
|
|
3692
|
+
- "create_merge_request_note"
|
|
3693
|
+
ui_log_events:
|
|
3694
|
+
- "on_tool_execution_success"
|
|
3695
|
+
- "on_tool_execution_failed"
|
|
3696
|
+
|
|
3697
|
+
routers:
|
|
3698
|
+
- from: "fix_pipeline_context"
|
|
3699
|
+
to: "fix_pipeline_next_decide_approach"
|
|
3700
|
+
- from: "fix_pipeline_next_decide_approach"
|
|
3701
|
+
condition:
|
|
3702
|
+
input: "context:fix_pipeline_next_decide_approach.final_answer.decision"
|
|
3703
|
+
routes:
|
|
3704
|
+
"add_comment": "fix_pipeline_add_comment"
|
|
3705
|
+
"create_fix_on_new_mr": "create_repository_branch"
|
|
3706
|
+
"create_fix_on_existing_mr": "fix_pipeline_next_checkout_existing_branch"
|
|
3707
|
+
"default_route": "fix_pipeline_add_comment"
|
|
3708
|
+
- from: "fix_pipeline_add_comment"
|
|
3709
|
+
to: "end"
|
|
3710
|
+
- from: "fix_pipeline_next_checkout_existing_branch"
|
|
3711
|
+
to: "fix_pipeline_create_plan"
|
|
3712
|
+
- from: "create_repository_branch"
|
|
3713
|
+
to: "fix_pipeline_create_plan"
|
|
3714
|
+
- from: "fix_pipeline_create_plan"
|
|
3715
|
+
to: "fix_pipeline_execution"
|
|
3716
|
+
- from: "fix_pipeline_execution"
|
|
3717
|
+
to: "fix_pipeline_git_commit"
|
|
3718
|
+
- from: "fix_pipeline_git_commit"
|
|
3719
|
+
to: "fix_pipeline_git_push"
|
|
3720
|
+
- from: "fix_pipeline_git_push"
|
|
3721
|
+
condition:
|
|
3722
|
+
input: "context:fix_pipeline_next_decide_approach.final_answer.decision"
|
|
3723
|
+
routes:
|
|
3724
|
+
"create_fix_on_existing_mr": "fix_pipeline_next_comment_existing_mr"
|
|
3725
|
+
"create_fix_on_new_mr": "fix_pipeline_next_create_new_mr"
|
|
3726
|
+
"default_route": "fix_pipeline_next_create_new_mr"
|
|
3727
|
+
- from: "fix_pipeline_next_comment_existing_mr"
|
|
3728
|
+
to: "end"
|
|
3729
|
+
- from: "fix_pipeline_next_create_new_mr"
|
|
3730
|
+
to: "end"
|
|
3731
|
+
|
|
3732
|
+
flow:
|
|
3733
|
+
entry_point: "fix_pipeline_context"
|
|
3734
|
+
inputs:
|
|
3735
|
+
- category: merge_request
|
|
3736
|
+
input_schema:
|
|
3737
|
+
url:
|
|
3738
|
+
type: string
|
|
3739
|
+
format: uri
|
|
3740
|
+
description: The URL for the GitLab Merge Request
|
|
3741
|
+
- category: pipeline
|
|
3742
|
+
input_schema:
|
|
3743
|
+
source_branch:
|
|
3744
|
+
type: string
|
|
3745
|
+
- category: agent_platform_standard_context
|
|
3746
|
+
input_schema:
|
|
3747
|
+
workload_branch:
|
|
3748
|
+
type: string
|
|
3749
|
+
description: The workload branch for the GitLab Merge Request
|
|
3750
|
+
primary_branch:
|
|
3751
|
+
type: string
|
|
3752
|
+
description: Merge Request target branch
|
|
3753
|
+
session_owner_id:
|
|
3754
|
+
type: string
|
|
3755
|
+
description: Human user's ID that initiated the flow
|
|
3756
|
+
`,
|
|
3757
|
+
"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',
|
|
3758
|
+
"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',
|
|
3759
|
+
"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',
|
|
3760
|
+
"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',
|
|
3761
|
+
"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'
|
|
3762
|
+
};
|
|
3763
|
+
|
|
2590
3764
|
// src/catalog.ts
|
|
3765
|
+
function extractFlowInputs(flowConfig) {
|
|
3766
|
+
if (!flowConfig) return [];
|
|
3767
|
+
const flow = flowConfig.flow;
|
|
3768
|
+
if (!flow?.inputs) return [];
|
|
3769
|
+
const inputs = flow.inputs;
|
|
3770
|
+
return inputs.map((inp) => ({
|
|
3771
|
+
category: inp.category,
|
|
3772
|
+
fields: Object.fromEntries(
|
|
3773
|
+
Object.entries(inp.input_schema ?? {}).map(([k, v]) => [
|
|
3774
|
+
k,
|
|
3775
|
+
{ type: v.type, description: v.description, format: v.format }
|
|
3776
|
+
])
|
|
3777
|
+
)
|
|
3778
|
+
}));
|
|
3779
|
+
}
|
|
2591
3780
|
var FOUNDATIONAL_QUERY = `
|
|
2592
3781
|
query AiFoundationalChatAgents($projectId: ProjectID!, $after: String) {
|
|
2593
3782
|
aiFoundationalChatAgents(projectId: $projectId, first: 50, after: $after) {
|
|
@@ -2600,12 +3789,14 @@ query AiCatalogCustomAgents($projectId: ProjectID!, $after: String) {
|
|
|
2600
3789
|
aiCatalogConfiguredItems(first: 50, projectId: $projectId, after: $after) {
|
|
2601
3790
|
pageInfo { hasNextPage endCursor }
|
|
2602
3791
|
nodes {
|
|
3792
|
+
id
|
|
2603
3793
|
enabled
|
|
2604
3794
|
item {
|
|
2605
3795
|
id
|
|
2606
3796
|
name
|
|
2607
3797
|
description
|
|
2608
3798
|
foundational
|
|
3799
|
+
foundationalFlowReference
|
|
2609
3800
|
itemType
|
|
2610
3801
|
latestVersion { id }
|
|
2611
3802
|
}
|
|
@@ -2616,6 +3807,16 @@ var FLOW_CONFIG_QUERY = `
|
|
|
2616
3807
|
query AiCatalogAgentFlowConfig($versionId: AiCatalogItemVersionID!) {
|
|
2617
3808
|
aiCatalogAgentFlowConfig(agentVersionId: $versionId, flowConfigType: CHAT)
|
|
2618
3809
|
}`;
|
|
3810
|
+
var FLOW_VERSION_DEFINITION_QUERY = `
|
|
3811
|
+
query FlowVersionDefinition($itemId: AiCatalogItemID!) {
|
|
3812
|
+
aiCatalogItem(id: $itemId) {
|
|
3813
|
+
latestVersion {
|
|
3814
|
+
... on AiCatalogFlowVersion {
|
|
3815
|
+
definition
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
}`;
|
|
2619
3820
|
async function gql(instanceUrl, token, query, variables) {
|
|
2620
3821
|
const res = await fetch(`${instanceUrl.replace(/\/$/, "")}/api/graphql`, {
|
|
2621
3822
|
method: "POST",
|
|
@@ -2666,27 +3867,43 @@ async function fetchCustomAgents(instanceUrl, token, projectId) {
|
|
|
2666
3867
|
if (!node.enabled) continue;
|
|
2667
3868
|
const item = node.item;
|
|
2668
3869
|
if (!item) continue;
|
|
2669
|
-
if (
|
|
3870
|
+
if (!item.latestVersion?.id) continue;
|
|
3871
|
+
const validTypes = ["AGENT", "FLOW"];
|
|
3872
|
+
if (!validTypes.includes(item.itemType)) continue;
|
|
3873
|
+
if (item.foundational && item.itemType === "AGENT") continue;
|
|
2670
3874
|
if (seen.has(item.id)) continue;
|
|
2671
3875
|
seen.add(item.id);
|
|
3876
|
+
const consumerNumericId = node.id ? parseInt(String(node.id).split("/").pop() ?? "", 10) || void 0 : void 0;
|
|
2672
3877
|
try {
|
|
2673
3878
|
const cfgData = await gql(instanceUrl, token, FLOW_CONFIG_QUERY, {
|
|
2674
3879
|
versionId: item.latestVersion.id
|
|
2675
3880
|
});
|
|
2676
3881
|
const yamlStr = cfgData?.aiCatalogAgentFlowConfig;
|
|
2677
|
-
|
|
2678
|
-
const parsed = load(yamlStr);
|
|
3882
|
+
const parsed = yamlStr ? load(yamlStr) : void 0;
|
|
2679
3883
|
agents.push({
|
|
2680
3884
|
identifier: item.id,
|
|
2681
3885
|
name: item.name,
|
|
2682
3886
|
description: item.description ?? "",
|
|
3887
|
+
itemType: item.itemType,
|
|
3888
|
+
workflowDefinition: item.foundationalFlowReference ?? void 0,
|
|
2683
3889
|
flowConfig: parsed,
|
|
2684
|
-
flowConfigSchemaVersion: parsed?.version ?? "v1",
|
|
2685
|
-
foundational:
|
|
2686
|
-
|
|
2687
|
-
|
|
3890
|
+
flowConfigSchemaVersion: parsed ? parsed?.version ?? "v1" : void 0,
|
|
3891
|
+
foundational: !!item.foundational,
|
|
3892
|
+
catalogItemVersionId: parseInt(item.latestVersion.id.split("/").pop() ?? "", 10) || void 0,
|
|
3893
|
+
consumerId: consumerNumericId,
|
|
3894
|
+
flowInputs: extractFlowInputs(parsed)
|
|
2688
3895
|
});
|
|
2689
3896
|
} catch {
|
|
3897
|
+
agents.push({
|
|
3898
|
+
identifier: item.id,
|
|
3899
|
+
name: item.name,
|
|
3900
|
+
description: item.description ?? "",
|
|
3901
|
+
itemType: item.itemType,
|
|
3902
|
+
workflowDefinition: item.foundationalFlowReference ?? void 0,
|
|
3903
|
+
foundational: !!item.foundational,
|
|
3904
|
+
catalogItemVersionId: parseInt(item.latestVersion.id.split("/").pop() ?? "", 10) || void 0,
|
|
3905
|
+
consumerId: consumerNumericId
|
|
3906
|
+
});
|
|
2690
3907
|
}
|
|
2691
3908
|
}
|
|
2692
3909
|
if (!page.pageInfo?.hasNextPage) break;
|
|
@@ -2694,6 +3911,145 @@ async function fetchCustomAgents(instanceUrl, token, projectId) {
|
|
|
2694
3911
|
}
|
|
2695
3912
|
return agents;
|
|
2696
3913
|
}
|
|
3914
|
+
async function getFlowDefinition(instanceUrl, token, opts) {
|
|
3915
|
+
const name = opts.flowName ?? `consumer-${opts.consumerId}`;
|
|
3916
|
+
let config = null;
|
|
3917
|
+
let apiError;
|
|
3918
|
+
if (opts.foundational && opts.workflowDefinition) {
|
|
3919
|
+
const flowKey = opts.workflowDefinition.split("/")[0];
|
|
3920
|
+
config = FOUNDATIONAL_FLOWS[flowKey] ?? null;
|
|
3921
|
+
}
|
|
3922
|
+
if (!config && !opts.foundational && opts.itemIdentifier) {
|
|
3923
|
+
try {
|
|
3924
|
+
const data = await gql(instanceUrl, token, FLOW_VERSION_DEFINITION_QUERY, {
|
|
3925
|
+
itemId: opts.itemIdentifier
|
|
3926
|
+
});
|
|
3927
|
+
config = data?.aiCatalogItem?.latestVersion?.definition ?? null;
|
|
3928
|
+
} catch (err) {
|
|
3929
|
+
apiError = err?.message ?? "Unknown error fetching flow definition";
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
if (!config && !opts.foundational && opts.catalogItemVersionId) {
|
|
3933
|
+
const versionGid = `gid://gitlab/Ai::Catalog::ItemVersion/${opts.catalogItemVersionId}`;
|
|
3934
|
+
try {
|
|
3935
|
+
const cfgData = await gql(instanceUrl, token, FLOW_CONFIG_QUERY, { versionId: versionGid });
|
|
3936
|
+
config = cfgData?.aiCatalogAgentFlowConfig ?? null;
|
|
3937
|
+
} catch (err) {
|
|
3938
|
+
if (!apiError) apiError = err?.message ?? "Unknown error fetching flow config";
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
return { config, name, ...apiError && !config ? { error: apiError } : {} };
|
|
3942
|
+
}
|
|
3943
|
+
var RESOLVE_ROOT_NAMESPACE_QUERY = `
|
|
3944
|
+
query resolveRootNamespace($projectPath: ID!) {
|
|
3945
|
+
project(fullPath: $projectPath) {
|
|
3946
|
+
id
|
|
3947
|
+
group {
|
|
3948
|
+
id
|
|
3949
|
+
rootNamespace { id }
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
}`;
|
|
3953
|
+
async function resolveRootNamespaceId(instanceUrl, token, projectPath) {
|
|
3954
|
+
try {
|
|
3955
|
+
const data = await gql(instanceUrl, token, RESOLVE_ROOT_NAMESPACE_QUERY, {
|
|
3956
|
+
projectPath
|
|
3957
|
+
});
|
|
3958
|
+
const rootGid = data?.project?.group?.rootNamespace?.id;
|
|
3959
|
+
if (rootGid) {
|
|
3960
|
+
return parseInt(rootGid.split("/").pop() ?? "", 10) || void 0;
|
|
3961
|
+
}
|
|
3962
|
+
} catch {
|
|
3963
|
+
}
|
|
3964
|
+
return void 0;
|
|
3965
|
+
}
|
|
3966
|
+
async function executeFlow(instanceUrl, token, projectPath, consumerId, goal, options) {
|
|
3967
|
+
const projectUrl = `${instanceUrl.replace(/\/$/, "")}/${projectPath}`;
|
|
3968
|
+
const additionalContext = [
|
|
3969
|
+
{
|
|
3970
|
+
Category: "agent_user_environment",
|
|
3971
|
+
Content: JSON.stringify({ project_url: projectUrl }),
|
|
3972
|
+
Metadata: "{}"
|
|
3973
|
+
}
|
|
3974
|
+
];
|
|
3975
|
+
if (options?.flowInputs) {
|
|
3976
|
+
for (const input of options.flowInputs) {
|
|
3977
|
+
additionalContext.push({
|
|
3978
|
+
Category: input.Category,
|
|
3979
|
+
Content: input.Content,
|
|
3980
|
+
Metadata: input.Metadata ?? "{}"
|
|
3981
|
+
});
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
const body = {
|
|
3985
|
+
project_id: projectPath,
|
|
3986
|
+
ai_catalog_item_consumer_id: consumerId,
|
|
3987
|
+
goal,
|
|
3988
|
+
start_workflow: true,
|
|
3989
|
+
environment: "ambient",
|
|
3990
|
+
agent_privileges: [1, 2, 3, 4, 5, 6],
|
|
3991
|
+
additional_context: additionalContext
|
|
3992
|
+
};
|
|
3993
|
+
if (options?.namespaceId) body.namespace_id = options.namespaceId;
|
|
3994
|
+
if (options?.issueId) body.issue_id = options.issueId;
|
|
3995
|
+
if (options?.mergeRequestId) body.merge_request_id = options.mergeRequestId;
|
|
3996
|
+
const rootNsId = await resolveRootNamespaceId(instanceUrl, token, projectPath);
|
|
3997
|
+
if (rootNsId) body.root_namespace_id = rootNsId;
|
|
3998
|
+
const res = await fetch(`${instanceUrl.replace(/\/$/, "")}/api/v4/ai/duo_workflows/workflows`, {
|
|
3999
|
+
method: "POST",
|
|
4000
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
4001
|
+
body: JSON.stringify(body)
|
|
4002
|
+
});
|
|
4003
|
+
if (!res.ok) {
|
|
4004
|
+
const text = await res.text();
|
|
4005
|
+
throw new Error(`Failed to execute flow (${res.status}): ${text}`);
|
|
4006
|
+
}
|
|
4007
|
+
return res.json();
|
|
4008
|
+
}
|
|
4009
|
+
var WORKFLOW_STATUS_QUERY = `
|
|
4010
|
+
query getWorkflowStatus($workflowId: AiDuoWorkflowsWorkflowID!) {
|
|
4011
|
+
duoWorkflowWorkflows(workflowId: $workflowId) {
|
|
4012
|
+
nodes {
|
|
4013
|
+
id
|
|
4014
|
+
status
|
|
4015
|
+
humanStatus
|
|
4016
|
+
createdAt
|
|
4017
|
+
updatedAt
|
|
4018
|
+
workflowDefinition
|
|
4019
|
+
lastExecutorLogsUrl
|
|
4020
|
+
latestCheckpoint {
|
|
4021
|
+
duoMessages {
|
|
4022
|
+
content
|
|
4023
|
+
correlationId
|
|
4024
|
+
role
|
|
4025
|
+
messageType
|
|
4026
|
+
status
|
|
4027
|
+
timestamp
|
|
4028
|
+
toolInfo
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
}`;
|
|
4034
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
4035
|
+
async function getWorkflowStatus(instanceUrl, token, workflowId) {
|
|
4036
|
+
const gid = `gid://gitlab/Ai::DuoWorkflows::Workflow/${workflowId}`;
|
|
4037
|
+
const maxWait = 12e4;
|
|
4038
|
+
const pollInterval = 5e3;
|
|
4039
|
+
const start = Date.now();
|
|
4040
|
+
while (Date.now() - start < maxWait) {
|
|
4041
|
+
const data2 = await gql(instanceUrl, token, WORKFLOW_STATUS_QUERY, { workflowId: gid });
|
|
4042
|
+
const nodes2 = data2?.duoWorkflowWorkflows?.nodes;
|
|
4043
|
+
if (!nodes2?.length) throw new Error(`Workflow ${workflowId} not found`);
|
|
4044
|
+
const workflow = nodes2[0];
|
|
4045
|
+
if (workflow.status !== "CREATED") return workflow;
|
|
4046
|
+
await sleep(pollInterval);
|
|
4047
|
+
}
|
|
4048
|
+
const data = await gql(instanceUrl, token, WORKFLOW_STATUS_QUERY, { workflowId: gid });
|
|
4049
|
+
const nodes = data?.duoWorkflowWorkflows?.nodes;
|
|
4050
|
+
if (!nodes?.length) throw new Error(`Workflow ${workflowId} not found`);
|
|
4051
|
+
return nodes[0];
|
|
4052
|
+
}
|
|
2697
4053
|
async function fetchCatalogAgents(instanceUrl, token, projectId) {
|
|
2698
4054
|
try {
|
|
2699
4055
|
const [foundational, custom] = await Promise.all([
|
|
@@ -2705,6 +4061,212 @@ async function fetchCatalogAgents(instanceUrl, token, projectId) {
|
|
|
2705
4061
|
return [];
|
|
2706
4062
|
}
|
|
2707
4063
|
}
|
|
4064
|
+
var LIST_AI_CATALOG_ITEMS_QUERY = `
|
|
4065
|
+
query listAiCatalogItems(
|
|
4066
|
+
$itemTypes: [AiCatalogItemType!]
|
|
4067
|
+
$search: String
|
|
4068
|
+
$first: Int
|
|
4069
|
+
$after: String
|
|
4070
|
+
) {
|
|
4071
|
+
aiCatalogItems(
|
|
4072
|
+
itemTypes: $itemTypes
|
|
4073
|
+
search: $search
|
|
4074
|
+
first: $first
|
|
4075
|
+
after: $after
|
|
4076
|
+
) {
|
|
4077
|
+
nodes {
|
|
4078
|
+
id
|
|
4079
|
+
name
|
|
4080
|
+
description
|
|
4081
|
+
itemType
|
|
4082
|
+
foundational
|
|
4083
|
+
public
|
|
4084
|
+
createdAt
|
|
4085
|
+
updatedAt
|
|
4086
|
+
softDeleted
|
|
4087
|
+
project { id, nameWithNamespace }
|
|
4088
|
+
latestVersion {
|
|
4089
|
+
id
|
|
4090
|
+
createdAt
|
|
4091
|
+
updatedAt
|
|
4092
|
+
createdBy { id, name, username }
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
pageInfo { hasNextPage, hasPreviousPage, startCursor, endCursor }
|
|
4096
|
+
}
|
|
4097
|
+
}`;
|
|
4098
|
+
var GET_AI_CATALOG_ITEM_QUERY = `
|
|
4099
|
+
query getAiCatalogItem($id: AiCatalogItemID!) {
|
|
4100
|
+
aiCatalogItem(id: $id) {
|
|
4101
|
+
id
|
|
4102
|
+
name
|
|
4103
|
+
description
|
|
4104
|
+
itemType
|
|
4105
|
+
foundational
|
|
4106
|
+
public
|
|
4107
|
+
createdAt
|
|
4108
|
+
updatedAt
|
|
4109
|
+
softDeleted
|
|
4110
|
+
project { id, nameWithNamespace }
|
|
4111
|
+
latestVersion {
|
|
4112
|
+
id
|
|
4113
|
+
createdAt
|
|
4114
|
+
updatedAt
|
|
4115
|
+
createdBy { id, name, username, webUrl }
|
|
4116
|
+
}
|
|
4117
|
+
userPermissions { adminAiCatalogItem, reportAiCatalogItem }
|
|
4118
|
+
}
|
|
4119
|
+
}`;
|
|
4120
|
+
var LIST_PROJECT_CONFIGURED_ITEMS_QUERY = `
|
|
4121
|
+
query listProjectConfiguredItems(
|
|
4122
|
+
$projectId: ProjectID!
|
|
4123
|
+
$itemTypes: [AiCatalogItemType!]
|
|
4124
|
+
$includeFoundationalConsumers: Boolean
|
|
4125
|
+
$first: Int
|
|
4126
|
+
$after: String
|
|
4127
|
+
) {
|
|
4128
|
+
aiCatalogConfiguredItems(
|
|
4129
|
+
projectId: $projectId
|
|
4130
|
+
itemTypes: $itemTypes
|
|
4131
|
+
includeFoundationalConsumers: $includeFoundationalConsumers
|
|
4132
|
+
first: $first
|
|
4133
|
+
after: $after
|
|
4134
|
+
) {
|
|
4135
|
+
nodes {
|
|
4136
|
+
id
|
|
4137
|
+
item {
|
|
4138
|
+
id
|
|
4139
|
+
name
|
|
4140
|
+
description
|
|
4141
|
+
itemType
|
|
4142
|
+
foundational
|
|
4143
|
+
public
|
|
4144
|
+
createdAt
|
|
4145
|
+
updatedAt
|
|
4146
|
+
softDeleted
|
|
4147
|
+
project { id, nameWithNamespace }
|
|
4148
|
+
latestVersion {
|
|
4149
|
+
id
|
|
4150
|
+
createdAt
|
|
4151
|
+
createdBy { name, username }
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
4154
|
+
pinnedItemVersion { id, humanVersionName }
|
|
4155
|
+
}
|
|
4156
|
+
pageInfo { hasNextPage, hasPreviousPage, startCursor, endCursor }
|
|
4157
|
+
}
|
|
4158
|
+
}`;
|
|
4159
|
+
var ENABLE_AI_CATALOG_ITEM_MUTATION = `
|
|
4160
|
+
mutation enableAiCatalogItem($input: AiCatalogItemConsumerCreateInput!) {
|
|
4161
|
+
aiCatalogItemConsumerCreate(input: $input) {
|
|
4162
|
+
errors
|
|
4163
|
+
itemConsumer {
|
|
4164
|
+
id
|
|
4165
|
+
project { id, name, webUrl }
|
|
4166
|
+
group { id, name, webUrl }
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4169
|
+
}`;
|
|
4170
|
+
var DISABLE_AI_CATALOG_ITEM_MUTATION = `
|
|
4171
|
+
mutation disableAiCatalogItem($input: AiCatalogItemConsumerDeleteInput!) {
|
|
4172
|
+
aiCatalogItemConsumerDelete(input: $input) {
|
|
4173
|
+
errors
|
|
4174
|
+
success
|
|
4175
|
+
}
|
|
4176
|
+
}`;
|
|
4177
|
+
var FIND_ITEM_CONSUMER_FOR_DISABLE_QUERY = `
|
|
4178
|
+
query findItemConsumer(
|
|
4179
|
+
$projectId: ProjectID!
|
|
4180
|
+
$itemTypes: [AiCatalogItemType!]
|
|
4181
|
+
) {
|
|
4182
|
+
aiCatalogConfiguredItems(
|
|
4183
|
+
projectId: $projectId
|
|
4184
|
+
itemTypes: $itemTypes
|
|
4185
|
+
includeFoundationalConsumers: true
|
|
4186
|
+
first: 100
|
|
4187
|
+
) {
|
|
4188
|
+
nodes { id, item { id } }
|
|
4189
|
+
}
|
|
4190
|
+
}`;
|
|
4191
|
+
var RESOLVE_PROJECT_IDS_FOR_TOOLS_QUERY = `
|
|
4192
|
+
query resolveProjectIds($projectPath: ID!) {
|
|
4193
|
+
project(fullPath: $projectPath) {
|
|
4194
|
+
id
|
|
4195
|
+
namespace { id }
|
|
4196
|
+
}
|
|
4197
|
+
}`;
|
|
4198
|
+
function normalizeItemGid(id) {
|
|
4199
|
+
if (id.startsWith("gid://")) return id;
|
|
4200
|
+
if (!/^\d+$/.test(id)) throw new Error(`Invalid catalog item ID: "${id}"`);
|
|
4201
|
+
return `gid://gitlab/Ai::Catalog::Item/${id}`;
|
|
4202
|
+
}
|
|
4203
|
+
async function listAiCatalogItems(instanceUrl, token, itemTypes, options) {
|
|
4204
|
+
const variables = {
|
|
4205
|
+
itemTypes,
|
|
4206
|
+
first: options?.first ?? 20
|
|
4207
|
+
};
|
|
4208
|
+
if (options?.search) variables.search = options.search;
|
|
4209
|
+
if (options?.after) variables.after = options.after;
|
|
4210
|
+
const result = await gql(instanceUrl, token, LIST_AI_CATALOG_ITEMS_QUERY, variables);
|
|
4211
|
+
return result.aiCatalogItems;
|
|
4212
|
+
}
|
|
4213
|
+
async function getAiCatalogItem(instanceUrl, token, itemId) {
|
|
4214
|
+
const gid = normalizeItemGid(itemId);
|
|
4215
|
+
const result = await gql(instanceUrl, token, GET_AI_CATALOG_ITEM_QUERY, { id: gid });
|
|
4216
|
+
return result.aiCatalogItem;
|
|
4217
|
+
}
|
|
4218
|
+
async function resolveProjectGid(instanceUrl, token, projectPath) {
|
|
4219
|
+
const result = await gql(instanceUrl, token, RESOLVE_PROJECT_IDS_FOR_TOOLS_QUERY, {
|
|
4220
|
+
projectPath
|
|
4221
|
+
});
|
|
4222
|
+
return result.project.id;
|
|
4223
|
+
}
|
|
4224
|
+
async function listProjectAiCatalogItems(instanceUrl, token, projectPath, itemTypes, options) {
|
|
4225
|
+
const projectGid = await resolveProjectGid(instanceUrl, token, projectPath);
|
|
4226
|
+
const variables = {
|
|
4227
|
+
projectId: projectGid,
|
|
4228
|
+
itemTypes,
|
|
4229
|
+
includeFoundationalConsumers: true,
|
|
4230
|
+
first: options?.first ?? 20
|
|
4231
|
+
};
|
|
4232
|
+
if (options?.after) variables.after = options.after;
|
|
4233
|
+
const result = await gql(instanceUrl, token, LIST_PROJECT_CONFIGURED_ITEMS_QUERY, variables);
|
|
4234
|
+
return result.aiCatalogConfiguredItems;
|
|
4235
|
+
}
|
|
4236
|
+
async function enableAiCatalogItemForProject(instanceUrl, token, projectPath, itemId) {
|
|
4237
|
+
const projectGid = await resolveProjectGid(instanceUrl, token, projectPath);
|
|
4238
|
+
const gid = normalizeItemGid(itemId);
|
|
4239
|
+
const result = await gql(instanceUrl, token, ENABLE_AI_CATALOG_ITEM_MUTATION, {
|
|
4240
|
+
input: { itemId: gid, target: { projectId: projectGid } }
|
|
4241
|
+
});
|
|
4242
|
+
if (result.aiCatalogItemConsumerCreate.errors.length > 0) {
|
|
4243
|
+
throw new Error(
|
|
4244
|
+
`Failed to enable item: ${result.aiCatalogItemConsumerCreate.errors.join(", ")}`
|
|
4245
|
+
);
|
|
4246
|
+
}
|
|
4247
|
+
return result.aiCatalogItemConsumerCreate;
|
|
4248
|
+
}
|
|
4249
|
+
async function disableAiCatalogItemForProject(instanceUrl, token, projectPath, itemId) {
|
|
4250
|
+
const projectGid = await resolveProjectGid(instanceUrl, token, projectPath);
|
|
4251
|
+
const gid = normalizeItemGid(itemId);
|
|
4252
|
+
const consumerResult = await gql(instanceUrl, token, FIND_ITEM_CONSUMER_FOR_DISABLE_QUERY, {
|
|
4253
|
+
projectId: projectGid,
|
|
4254
|
+
itemTypes: ["AGENT", "FLOW", "THIRD_PARTY_FLOW"]
|
|
4255
|
+
});
|
|
4256
|
+
const consumer = consumerResult.aiCatalogConfiguredItems.nodes.find(
|
|
4257
|
+
(n) => n.item.id === gid
|
|
4258
|
+
);
|
|
4259
|
+
if (!consumer?.id) throw new Error("Agent/flow is not enabled in this project");
|
|
4260
|
+
const result = await gql(instanceUrl, token, DISABLE_AI_CATALOG_ITEM_MUTATION, {
|
|
4261
|
+
input: { id: consumer.id }
|
|
4262
|
+
});
|
|
4263
|
+
if (result.aiCatalogItemConsumerDelete.errors.length > 0) {
|
|
4264
|
+
throw new Error(
|
|
4265
|
+
`Failed to disable item: ${result.aiCatalogItemConsumerDelete.errors.join(", ")}`
|
|
4266
|
+
);
|
|
4267
|
+
}
|
|
4268
|
+
return result.aiCatalogItemConsumerDelete;
|
|
4269
|
+
}
|
|
2708
4270
|
|
|
2709
4271
|
// src/agents.ts
|
|
2710
4272
|
function resolveModelId(entry) {
|
|
@@ -2714,22 +4276,144 @@ function resolveModelId(entry) {
|
|
|
2714
4276
|
}
|
|
2715
4277
|
|
|
2716
4278
|
// src/index.ts
|
|
2717
|
-
import { readFileSync
|
|
4279
|
+
import { readFileSync } from "fs";
|
|
2718
4280
|
import { join } from "path";
|
|
2719
4281
|
import os from "os";
|
|
2720
|
-
var
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
4282
|
+
var z = tool.schema;
|
|
4283
|
+
function makeItemTools(z2, itemType, label, getAuth, ensureAuth, onChanged) {
|
|
4284
|
+
const itemTypes = [itemType];
|
|
4285
|
+
const Label = label.charAt(0).toUpperCase() + label.slice(1);
|
|
4286
|
+
return {
|
|
4287
|
+
[`gitlab_list_${label}s`]: tool({
|
|
4288
|
+
description: `List ${label}s in the GitLab AI Catalog.
|
|
4289
|
+
Returns ${label}s with name, description, visibility, foundational flag, and version info.
|
|
4290
|
+
Supports search and cursor-based pagination.`,
|
|
4291
|
+
args: {
|
|
4292
|
+
search: z2.string().optional().describe(`Search query to filter ${label}s by name or description`),
|
|
4293
|
+
first: z2.number().optional().describe("Number of items to return (default 20)"),
|
|
4294
|
+
after: z2.string().optional().describe("Cursor for pagination (from pageInfo.endCursor)")
|
|
4295
|
+
},
|
|
4296
|
+
execute: async (args) => {
|
|
4297
|
+
const auth = ensureAuth();
|
|
4298
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4299
|
+
try {
|
|
4300
|
+
const result = await listAiCatalogItems(auth.instanceUrl, auth.token, itemTypes, args);
|
|
4301
|
+
return JSON.stringify(result, null, 2);
|
|
4302
|
+
} catch (err) {
|
|
4303
|
+
return `Error: ${err.message}`;
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
}),
|
|
4307
|
+
[`gitlab_get_${label}`]: tool({
|
|
4308
|
+
description: `Get details of a specific ${label} from the AI Catalog.
|
|
4309
|
+
Returns full details including name, description, visibility, versions, creator, and permissions.
|
|
4310
|
+
Accepts either a full Global ID (gid://gitlab/Ai::Catalog::Item/123) or just the numeric ID.`,
|
|
4311
|
+
args: {
|
|
4312
|
+
[`${label}_id`]: z2.string().describe(`${Label} ID: full GID or numeric ID`)
|
|
4313
|
+
},
|
|
4314
|
+
execute: async (args) => {
|
|
4315
|
+
const auth = ensureAuth();
|
|
4316
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4317
|
+
try {
|
|
4318
|
+
const result = await getAiCatalogItem(auth.instanceUrl, auth.token, args[`${label}_id`]);
|
|
4319
|
+
return JSON.stringify(result, null, 2);
|
|
4320
|
+
} catch (err) {
|
|
4321
|
+
return `Error: ${err.message}`;
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
}),
|
|
4325
|
+
[`gitlab_list_project_${label}s`]: tool({
|
|
4326
|
+
description: `List ${label}s enabled for a specific project.
|
|
4327
|
+
Returns all configured ${label}s including foundational ones.
|
|
4328
|
+
Each result includes the ${label} details and its consumer configuration.`,
|
|
4329
|
+
args: {
|
|
4330
|
+
project_id: z2.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
|
|
4331
|
+
first: z2.number().optional().describe("Number of items to return (default 20)"),
|
|
4332
|
+
after: z2.string().optional().describe("Cursor for pagination")
|
|
4333
|
+
},
|
|
4334
|
+
execute: async (args) => {
|
|
4335
|
+
const auth = ensureAuth();
|
|
4336
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4337
|
+
try {
|
|
4338
|
+
const result = await listProjectAiCatalogItems(
|
|
4339
|
+
auth.instanceUrl,
|
|
4340
|
+
auth.token,
|
|
4341
|
+
args.project_id,
|
|
4342
|
+
itemTypes,
|
|
4343
|
+
{ first: args.first, after: args.after }
|
|
4344
|
+
);
|
|
4345
|
+
return JSON.stringify(result, null, 2);
|
|
4346
|
+
} catch (err) {
|
|
4347
|
+
return `Error: ${err.message}`;
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
}),
|
|
4351
|
+
[`gitlab_enable_project_${label}`]: tool({
|
|
4352
|
+
description: `Enable a ${label} in a project.
|
|
4353
|
+
Requires Maintainer or Owner role.
|
|
4354
|
+
Enabling in a project also enables at the group level.
|
|
4355
|
+
Foundational ${label}s cannot be enabled this way (use admin settings).`,
|
|
4356
|
+
args: {
|
|
4357
|
+
project_id: z2.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
|
|
4358
|
+
[`${label}_id`]: z2.string().describe(`${Label} ID: full GID or numeric ID`)
|
|
4359
|
+
},
|
|
4360
|
+
execute: async (args) => {
|
|
4361
|
+
const auth = ensureAuth();
|
|
4362
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4363
|
+
try {
|
|
4364
|
+
const result = await enableAiCatalogItemForProject(
|
|
4365
|
+
auth.instanceUrl,
|
|
4366
|
+
auth.token,
|
|
4367
|
+
args.project_id,
|
|
4368
|
+
args[`${label}_id`]
|
|
4369
|
+
);
|
|
4370
|
+
await onChanged?.();
|
|
4371
|
+
return JSON.stringify(result, null, 2) + "\n\nNote: Restart opencode for the @ menu to reflect changes.";
|
|
4372
|
+
} catch (err) {
|
|
4373
|
+
return `Error: ${err.message}`;
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
}),
|
|
4377
|
+
[`gitlab_disable_project_${label}`]: tool({
|
|
4378
|
+
description: `Disable a ${label} in a project.
|
|
4379
|
+
Requires Maintainer or Owner role.
|
|
4380
|
+
Resolves the consumer ID internally from the ${label} ID.`,
|
|
4381
|
+
args: {
|
|
4382
|
+
project_id: z2.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
|
|
4383
|
+
[`${label}_id`]: z2.string().describe(`${Label} ID: full GID or numeric ID`)
|
|
4384
|
+
},
|
|
4385
|
+
execute: async (args) => {
|
|
4386
|
+
const auth = ensureAuth();
|
|
4387
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4388
|
+
try {
|
|
4389
|
+
const result = await disableAiCatalogItemForProject(
|
|
4390
|
+
auth.instanceUrl,
|
|
4391
|
+
auth.token,
|
|
4392
|
+
args.project_id,
|
|
4393
|
+
args[`${label}_id`]
|
|
4394
|
+
);
|
|
4395
|
+
await onChanged?.();
|
|
4396
|
+
return JSON.stringify(result, null, 2) + "\n\nNote: Restart opencode for the @ menu to reflect changes.";
|
|
4397
|
+
} catch (err) {
|
|
4398
|
+
return `Error: ${err.message}`;
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
})
|
|
4402
|
+
};
|
|
4403
|
+
}
|
|
4404
|
+
function makeAgentFlowTools(z2, getAuth, readAuthFn, setAuth, onChanged) {
|
|
4405
|
+
const ensureAuth = () => {
|
|
4406
|
+
let auth = getAuth();
|
|
4407
|
+
if (!auth) {
|
|
4408
|
+
auth = readAuthFn();
|
|
4409
|
+
if (auth) setAuth(auth);
|
|
4410
|
+
}
|
|
4411
|
+
return auth;
|
|
4412
|
+
};
|
|
4413
|
+
return {
|
|
4414
|
+
...makeItemTools(z2, "AGENT", "agent", getAuth, ensureAuth, onChanged),
|
|
4415
|
+
...makeItemTools(z2, "FLOW", "flow", getAuth, ensureAuth, onChanged)
|
|
4416
|
+
};
|
|
2733
4417
|
}
|
|
2734
4418
|
function readAuth() {
|
|
2735
4419
|
try {
|
|
@@ -2747,54 +4431,300 @@ function readAuth() {
|
|
|
2747
4431
|
}
|
|
2748
4432
|
var memo = /* @__PURE__ */ new Map();
|
|
2749
4433
|
var plugin = async (input) => {
|
|
2750
|
-
|
|
4434
|
+
let authCache = null;
|
|
4435
|
+
let projectPath;
|
|
4436
|
+
let namespaceId;
|
|
4437
|
+
const flowAgents = /* @__PURE__ */ new Map();
|
|
4438
|
+
let cfgRef = null;
|
|
4439
|
+
let baseModelIdRef;
|
|
2751
4440
|
async function load2() {
|
|
2752
4441
|
const auth = readAuth();
|
|
2753
|
-
log("readAuth result:", auth ? `instanceUrl=${auth.instanceUrl}` : "null");
|
|
2754
4442
|
if (!auth) return null;
|
|
4443
|
+
authCache = auth;
|
|
2755
4444
|
const { token, instanceUrl } = auth;
|
|
2756
|
-
log("instanceUrl:", instanceUrl);
|
|
2757
4445
|
const cache = new GitLabModelCache(input.directory, instanceUrl);
|
|
2758
4446
|
const entry = cache.load();
|
|
2759
|
-
log(
|
|
2760
|
-
"cache entry:",
|
|
2761
|
-
entry ? `discovery=${!!entry.discovery} projectId=${entry.project?.id}` : "null"
|
|
2762
|
-
);
|
|
2763
4447
|
if (!entry?.discovery) return null;
|
|
2764
4448
|
const projectId = entry.project?.id;
|
|
2765
4449
|
if (!projectId) return null;
|
|
4450
|
+
projectPath = entry.project?.pathWithNamespace;
|
|
4451
|
+
namespaceId = entry.project?.namespaceId;
|
|
2766
4452
|
const key = `${input.directory}\0${instanceUrl}`;
|
|
2767
4453
|
const cached = memo.get(key);
|
|
2768
4454
|
const agents = cached ?? await fetchCatalogAgents(instanceUrl, token, `gid://gitlab/Project/${projectId}`);
|
|
2769
|
-
log("agents fetched:", agents.length);
|
|
2770
4455
|
if (!cached) memo.set(key, agents);
|
|
2771
4456
|
return { agents, entry };
|
|
2772
4457
|
}
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
4458
|
+
async function refreshAgents() {
|
|
4459
|
+
if (!authCache || !cfgRef) return;
|
|
4460
|
+
const key = `${input.directory}\0${authCache.instanceUrl}`;
|
|
4461
|
+
memo.delete(key);
|
|
4462
|
+
const result = await load2();
|
|
4463
|
+
if (!result) return;
|
|
4464
|
+
const previousNames = /* @__PURE__ */ new Set([
|
|
4465
|
+
...flowAgents.keys(),
|
|
4466
|
+
...Object.keys(cfgRef.agent ?? {}).filter((n) => {
|
|
4467
|
+
const desc = cfgRef.agent[n]?.description ?? "";
|
|
4468
|
+
return desc.startsWith("[GitLab");
|
|
4469
|
+
})
|
|
4470
|
+
]);
|
|
4471
|
+
flowAgents.clear();
|
|
4472
|
+
const currentNames = /* @__PURE__ */ new Set();
|
|
4473
|
+
for (const agent of result.agents) {
|
|
4474
|
+
currentNames.add(agent.name);
|
|
4475
|
+
const isFlow = agent.itemType === "FLOW";
|
|
4476
|
+
if (isFlow && agent.consumerId && projectPath) {
|
|
4477
|
+
flowAgents.set(agent.name, agent);
|
|
4478
|
+
cfgRef.agent[agent.name] = {
|
|
4479
|
+
name: agent.name,
|
|
4480
|
+
description: `[GitLab Flow] ${agent.description}`,
|
|
4481
|
+
mode: "subagent"
|
|
4482
|
+
};
|
|
4483
|
+
} else {
|
|
4484
|
+
cfgRef.agent[agent.name] = {
|
|
2782
4485
|
name: agent.name,
|
|
2783
4486
|
description: agent.foundational ? "[GitLab Foundational Agent]" : "[GitLab Custom Agent]",
|
|
2784
4487
|
mode: "primary",
|
|
2785
|
-
model: `gitlab/${
|
|
4488
|
+
model: `gitlab/${baseModelIdRef}`,
|
|
2786
4489
|
options: {
|
|
2787
4490
|
workflowDefinition: agent.workflowDefinition,
|
|
2788
4491
|
flowConfig: agent.flowConfig,
|
|
2789
4492
|
flowConfigSchemaVersion: agent.flowConfigSchemaVersion,
|
|
2790
|
-
// numeric version ID passed as ai_catalog_item_version_id in createWorkflow
|
|
2791
|
-
// so GitLab UI links the session to the correct custom agent page
|
|
2792
4493
|
aiCatalogItemVersionId: agent.catalogItemVersionId
|
|
2793
4494
|
},
|
|
2794
4495
|
permission: { "*": "allow" }
|
|
2795
4496
|
};
|
|
2796
4497
|
}
|
|
2797
|
-
|
|
4498
|
+
}
|
|
4499
|
+
for (const name of previousNames) {
|
|
4500
|
+
if (!currentNames.has(name)) {
|
|
4501
|
+
delete cfgRef.agent[name];
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
return {
|
|
4506
|
+
async config(cfg) {
|
|
4507
|
+
const result = await load2();
|
|
4508
|
+
if (!result?.agents.length) return;
|
|
4509
|
+
const baseModelId = resolveModelId(result.entry);
|
|
4510
|
+
cfg.agent ??= {};
|
|
4511
|
+
cfgRef = cfg;
|
|
4512
|
+
baseModelIdRef = baseModelId;
|
|
4513
|
+
for (const agent of result.agents) {
|
|
4514
|
+
const isFlow = agent.itemType === "FLOW";
|
|
4515
|
+
if (isFlow && agent.consumerId && projectPath) {
|
|
4516
|
+
flowAgents.set(agent.name, agent);
|
|
4517
|
+
cfg.agent[agent.name] = {
|
|
4518
|
+
name: agent.name,
|
|
4519
|
+
description: `[GitLab Flow] ${agent.description}`,
|
|
4520
|
+
mode: "subagent"
|
|
4521
|
+
};
|
|
4522
|
+
} else {
|
|
4523
|
+
cfg.agent[agent.name] = {
|
|
4524
|
+
name: agent.name,
|
|
4525
|
+
description: agent.foundational ? "[GitLab Foundational Agent]" : "[GitLab Custom Agent]",
|
|
4526
|
+
mode: "primary",
|
|
4527
|
+
model: `gitlab/${baseModelId}`,
|
|
4528
|
+
options: {
|
|
4529
|
+
workflowDefinition: agent.workflowDefinition,
|
|
4530
|
+
flowConfig: agent.flowConfig,
|
|
4531
|
+
flowConfigSchemaVersion: agent.flowConfigSchemaVersion,
|
|
4532
|
+
aiCatalogItemVersionId: agent.catalogItemVersionId
|
|
4533
|
+
},
|
|
4534
|
+
permission: { "*": "allow" }
|
|
4535
|
+
};
|
|
4536
|
+
}
|
|
4537
|
+
}
|
|
4538
|
+
},
|
|
4539
|
+
"chat.message": async (_input, output) => {
|
|
4540
|
+
const indicesToRemove = [];
|
|
4541
|
+
const replacements = [];
|
|
4542
|
+
for (let i = 0; i < output.parts.length; i++) {
|
|
4543
|
+
const part = output.parts[i];
|
|
4544
|
+
if (part.type !== "agent") continue;
|
|
4545
|
+
const flow = flowAgents.get(part.name);
|
|
4546
|
+
if (!flow || !flow.consumerId || !projectPath) continue;
|
|
4547
|
+
const rawText = output.parts.filter((p) => p.type === "text" && !p.synthetic).map((p) => p.text).join(" ").trim() || "Execute the flow";
|
|
4548
|
+
const baseUrl = authCache?.instanceUrl?.replace(/\/$/, "") ?? "https://gitlab.com";
|
|
4549
|
+
const projectUrl = `${baseUrl}/${projectPath}`;
|
|
4550
|
+
const subagentPrompt = [
|
|
4551
|
+
`Execute the "${flow.name}" GitLab flow on project ${projectPath} (${projectUrl}).`,
|
|
4552
|
+
`User goal: "${rawText}"`,
|
|
4553
|
+
``,
|
|
4554
|
+
`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.`,
|
|
4555
|
+
``,
|
|
4556
|
+
`STEP 1 - FETCH AND DISPLAY FLOW DEFINITION:`,
|
|
4557
|
+
`Call gitlab_get_flow_definition with consumer_id: ${flow.consumerId} and foundational: ${!!flow.foundational}.`,
|
|
4558
|
+
`The response has a "config" field containing YAML. Parse it and find the "flow:" \u2192 "inputs:" section.`,
|
|
4559
|
+
`Each input has a "category" and "input_schema" with field names and types.`,
|
|
4560
|
+
`Print ALL inputs you found. Skip "agent_platform_standard_context" (auto-injected by server).`,
|
|
4561
|
+
`If the config has no "inputs" or "inputs: []", say "No required inputs" and skip to step 3.`,
|
|
4562
|
+
``,
|
|
4563
|
+
`STEP 2 - GATHER INPUT VALUES:`,
|
|
4564
|
+
`For each required input from step 1, be creative and resourceful in finding the real values:`,
|
|
4565
|
+
`- Use ANY available tools to find the data: GitLab tools, MCP servers, search, file reads, etc.`,
|
|
4566
|
+
`- Look at the user's goal for hints (MR numbers, pipeline IDs, branch names, URLs)`,
|
|
4567
|
+
`- For URL fields: construct full GitLab URLs using base ${projectUrl}`,
|
|
4568
|
+
`- For branch fields: look up from MRs, pipelines, or project default branch`,
|
|
4569
|
+
`- For IDs: extract from the user's message or look up via GitLab API`,
|
|
4570
|
+
`- If an input cannot be determined, ask yourself what makes sense given the context`,
|
|
4571
|
+
`Print each resolved input value before proceeding.`,
|
|
4572
|
+
``,
|
|
4573
|
+
`STEP 3 - EXECUTE THE FLOW:`,
|
|
4574
|
+
`Call gitlab_execute_project_flow with:`,
|
|
4575
|
+
`- project_id: "${projectPath}"`,
|
|
4576
|
+
`- consumer_id: ${flow.consumerId}`,
|
|
4577
|
+
`- goal: the user's goal with any relevant URLs/context appended`,
|
|
4578
|
+
`- additional_context: JSON array of inputs from step 2, each entry: {"Category":"<category>","Content":"{\\"field\\":\\"value\\"}"}`,
|
|
4579
|
+
` Example: [{"Category":"merge_request","Content":"{\\"url\\":\\"${projectUrl}/-/merge_requests/12\\"}"}]`,
|
|
4580
|
+
``,
|
|
4581
|
+
`STEP 4 - CONFIRM FLOW STARTED:`,
|
|
4582
|
+
`Call gitlab_get_workflow_status with the returned workflow_id.`,
|
|
4583
|
+
`The tool blocks automatically while status is CREATED (up to 2 minutes).`,
|
|
4584
|
+
`Once it returns, report:`,
|
|
4585
|
+
`- The workflow status (RUNNING, FINISHED, FAILED, etc.)`,
|
|
4586
|
+
`- The workflow URL: ${projectUrl}/-/duo_workflows/<workflow_id>`,
|
|
4587
|
+
`- A brief summary of what was executed`,
|
|
4588
|
+
`Then stop. Do NOT keep polling. The user can follow the flow execution in the GitLab UI.`
|
|
4589
|
+
].join("\n");
|
|
4590
|
+
const resultText = [
|
|
4591
|
+
`IMPORTANT: You MUST call the Task tool RIGHT NOW to dispatch a subagent. Do NOT execute these steps yourself.`,
|
|
4592
|
+
``,
|
|
4593
|
+
`Call the Task tool with:`,
|
|
4594
|
+
` description: "Execute ${flow.name} flow"`,
|
|
4595
|
+
` prompt: ${JSON.stringify(subagentPrompt)}`,
|
|
4596
|
+
``,
|
|
4597
|
+
`Do not do anything else. Just call the Task tool with the above parameters.`
|
|
4598
|
+
].join("\n");
|
|
4599
|
+
replacements.push({ idx: i, text: resultText });
|
|
4600
|
+
if (i + 1 < output.parts.length) {
|
|
4601
|
+
const next = output.parts[i + 1];
|
|
4602
|
+
if (next.type === "text" && next.synthetic && next.text?.includes("call the task tool with subagent")) {
|
|
4603
|
+
indicesToRemove.push(i + 1);
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4607
|
+
for (const { idx, text } of replacements) {
|
|
4608
|
+
const original = output.parts[idx];
|
|
4609
|
+
output.parts[idx] = {
|
|
4610
|
+
...original,
|
|
4611
|
+
type: "text",
|
|
4612
|
+
text
|
|
4613
|
+
};
|
|
4614
|
+
delete output.parts[idx].name;
|
|
4615
|
+
delete output.parts[idx].source;
|
|
4616
|
+
}
|
|
4617
|
+
for (const idx of indicesToRemove.reverse()) {
|
|
4618
|
+
output.parts.splice(idx, 1);
|
|
4619
|
+
}
|
|
4620
|
+
},
|
|
4621
|
+
tool: {
|
|
4622
|
+
gitlab_execute_project_flow: tool({
|
|
4623
|
+
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.",
|
|
4624
|
+
args: {
|
|
4625
|
+
project_id: z.string().describe('Project path (e.g., "gitlab-org/gitlab")'),
|
|
4626
|
+
consumer_id: z.number().describe("AI Catalog ItemConsumer numeric ID"),
|
|
4627
|
+
goal: z.string().describe("User prompt/goal for the flow, include relevant URLs"),
|
|
4628
|
+
additional_context: z.string().optional().describe(
|
|
4629
|
+
'JSON array of flow inputs: [{"Category":"merge_request","Content":"{\\"url\\":\\"https://...\\"}"}]'
|
|
4630
|
+
),
|
|
4631
|
+
issue_id: z.number().optional().describe("Issue IID for context"),
|
|
4632
|
+
merge_request_id: z.number().optional().describe("Merge request IID for context")
|
|
4633
|
+
},
|
|
4634
|
+
execute: async (args) => {
|
|
4635
|
+
if (!authCache) {
|
|
4636
|
+
const auth = readAuth();
|
|
4637
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4638
|
+
authCache = auth;
|
|
4639
|
+
}
|
|
4640
|
+
let flowInputs;
|
|
4641
|
+
if (args.additional_context) {
|
|
4642
|
+
try {
|
|
4643
|
+
flowInputs = JSON.parse(args.additional_context);
|
|
4644
|
+
} catch {
|
|
4645
|
+
return "Error: additional_context must be a valid JSON array";
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
try {
|
|
4649
|
+
const result = await executeFlow(
|
|
4650
|
+
authCache.instanceUrl,
|
|
4651
|
+
authCache.token,
|
|
4652
|
+
args.project_id,
|
|
4653
|
+
args.consumer_id,
|
|
4654
|
+
args.goal,
|
|
4655
|
+
{
|
|
4656
|
+
issueId: args.issue_id,
|
|
4657
|
+
mergeRequestId: args.merge_request_id,
|
|
4658
|
+
namespaceId,
|
|
4659
|
+
flowInputs
|
|
4660
|
+
}
|
|
4661
|
+
);
|
|
4662
|
+
return JSON.stringify(result, null, 2);
|
|
4663
|
+
} catch (err) {
|
|
4664
|
+
return `Error executing flow: ${err.message}`;
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
}),
|
|
4668
|
+
gitlab_get_flow_definition: tool({
|
|
4669
|
+
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.",
|
|
4670
|
+
args: {
|
|
4671
|
+
consumer_id: z.number().describe("AI Catalog ItemConsumer numeric ID"),
|
|
4672
|
+
foundational: z.boolean().describe("true for GitLab foundational flows, false for custom flows")
|
|
4673
|
+
},
|
|
4674
|
+
execute: async (args) => {
|
|
4675
|
+
if (!authCache) {
|
|
4676
|
+
const auth = readAuth();
|
|
4677
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4678
|
+
authCache = auth;
|
|
4679
|
+
}
|
|
4680
|
+
const flow = [...flowAgents.values()].find((f) => f.consumerId === args.consumer_id);
|
|
4681
|
+
try {
|
|
4682
|
+
const result = await getFlowDefinition(authCache.instanceUrl, authCache.token, {
|
|
4683
|
+
consumerId: args.consumer_id,
|
|
4684
|
+
flowName: flow?.name,
|
|
4685
|
+
foundational: args.foundational,
|
|
4686
|
+
catalogItemVersionId: flow?.catalogItemVersionId,
|
|
4687
|
+
itemIdentifier: flow?.identifier,
|
|
4688
|
+
workflowDefinition: flow?.workflowDefinition
|
|
4689
|
+
});
|
|
4690
|
+
return JSON.stringify(result, null, 2);
|
|
4691
|
+
} catch (err) {
|
|
4692
|
+
return `Error getting flow definition: ${err.message}`;
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
}),
|
|
4696
|
+
gitlab_get_workflow_status: tool({
|
|
4697
|
+
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.",
|
|
4698
|
+
args: {
|
|
4699
|
+
workflow_id: z.number().describe("Workflow numeric ID (from gitlab_execute_project_flow result)")
|
|
4700
|
+
},
|
|
4701
|
+
execute: async (args) => {
|
|
4702
|
+
if (!authCache) {
|
|
4703
|
+
const auth = readAuth();
|
|
4704
|
+
if (!auth) return "Error: GitLab authentication not available";
|
|
4705
|
+
authCache = auth;
|
|
4706
|
+
}
|
|
4707
|
+
try {
|
|
4708
|
+
const result = await getWorkflowStatus(
|
|
4709
|
+
authCache.instanceUrl,
|
|
4710
|
+
authCache.token,
|
|
4711
|
+
args.workflow_id
|
|
4712
|
+
);
|
|
4713
|
+
return JSON.stringify(result, null, 2);
|
|
4714
|
+
} catch (err) {
|
|
4715
|
+
return `Error getting workflow status: ${err.message}`;
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4718
|
+
}),
|
|
4719
|
+
...makeAgentFlowTools(
|
|
4720
|
+
z,
|
|
4721
|
+
() => authCache,
|
|
4722
|
+
readAuth,
|
|
4723
|
+
(a) => {
|
|
4724
|
+
authCache = a;
|
|
4725
|
+
},
|
|
4726
|
+
refreshAgents
|
|
4727
|
+
)
|
|
2798
4728
|
}
|
|
2799
4729
|
};
|
|
2800
4730
|
};
|