opencode-gitlab-dap 1.1.2 → 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/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 (item.foundational || item.itemType !== "AGENT" || !item.latestVersion?.id) continue;
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
- if (!yamlStr) continue;
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: false,
2686
- // extract numeric ID from GID e.g. "gid://gitlab/Ai::Catalog::ItemVersion/1015082" 1015082
2687
- catalogItemVersionId: parseInt(item.latestVersion.id.split("/").pop() ?? "", 10) || void 0
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) {
@@ -2717,6 +4279,142 @@ function resolveModelId(entry) {
2717
4279
  import { readFileSync } from "fs";
2718
4280
  import { join } from "path";
2719
4281
  import os from "os";
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
+ };
4417
+ }
2720
4418
  function readAuth() {
2721
4419
  try {
2722
4420
  const authPath = join(os.homedir(), ".local", "share", "opencode", "auth.json");
@@ -2733,33 +4431,61 @@ function readAuth() {
2733
4431
  }
2734
4432
  var memo = /* @__PURE__ */ new Map();
2735
4433
  var plugin = async (input) => {
4434
+ let authCache = null;
4435
+ let projectPath;
4436
+ let namespaceId;
4437
+ const flowAgents = /* @__PURE__ */ new Map();
4438
+ let cfgRef = null;
4439
+ let baseModelIdRef;
2736
4440
  async function load2() {
2737
4441
  const auth = readAuth();
2738
4442
  if (!auth) return null;
4443
+ authCache = auth;
2739
4444
  const { token, instanceUrl } = auth;
2740
4445
  const cache = new GitLabModelCache(input.directory, instanceUrl);
2741
4446
  const entry = cache.load();
2742
4447
  if (!entry?.discovery) return null;
2743
4448
  const projectId = entry.project?.id;
2744
4449
  if (!projectId) return null;
4450
+ projectPath = entry.project?.pathWithNamespace;
4451
+ namespaceId = entry.project?.namespaceId;
2745
4452
  const key = `${input.directory}\0${instanceUrl}`;
2746
4453
  const cached = memo.get(key);
2747
4454
  const agents = cached ?? await fetchCatalogAgents(instanceUrl, token, `gid://gitlab/Project/${projectId}`);
2748
4455
  if (!cached) memo.set(key, agents);
2749
4456
  return { agents, entry };
2750
4457
  }
2751
- return {
2752
- async config(cfg) {
2753
- const result = await load2();
2754
- if (!result?.agents.length) return;
2755
- const baseModelId = resolveModelId(result.entry);
2756
- cfg.agent ??= {};
2757
- for (const agent of result.agents) {
2758
- cfg.agent[agent.name] = {
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] = {
2759
4485
  name: agent.name,
2760
4486
  description: agent.foundational ? "[GitLab Foundational Agent]" : "[GitLab Custom Agent]",
2761
4487
  mode: "primary",
2762
- model: `gitlab/${baseModelId}`,
4488
+ model: `gitlab/${baseModelIdRef}`,
2763
4489
  options: {
2764
4490
  workflowDefinition: agent.workflowDefinition,
2765
4491
  flowConfig: agent.flowConfig,
@@ -2770,6 +4496,236 @@ var plugin = async (input) => {
2770
4496
  };
2771
4497
  }
2772
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
+ )
4728
+ }
2773
4729
  };
2774
4730
  };
2775
4731
  var index_default = plugin;