Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions .github/workflows/shared/apm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
# APM (Agent Package Manager) - Shared Workflow
# Install Microsoft APM packages in your agentic workflow.
#
# This shared workflow wraps the Microsoft APM action to install and pack
# agent skill packages into an artifact that the agent job restores at runtime.
# It creates a dedicated packing job, uploads the bundle as a workflow artifact,
# and automatically restores it before the agent runs.
#
# Documentation: https://github.com/microsoft/APM
#
# Usage:
# imports:
# - uses: shared/apm.md
# with:
# packages:
# - microsoft/apm-sample-package
# - github/awesome-copilot/skills/review-and-refactor

import-schema:
packages:
type: array
items:
type: string
required: true
description: >
List of APM package references to install.
Format: owner/repo or owner/repo/path/to/skill.
Examples: microsoft/apm-sample-package, github/awesome-copilot/skills/review-and-refactor

dependencies:
packages: ${{ github.aw.import-inputs.packages }}
---

<!--
## APM Packages

The following APM packages are configured for this workflow:
- **Packages**: `${{ github.aw.import-inputs.packages }}`

These packages are installed and packed by a dedicated `apm` job that runs before the agent.
The packed bundle is uploaded as a workflow artifact and automatically restored at the start of
the agent job, making all package skills and tools available to the AI agent.

### How it works

1. **Pack job** (`apm`): Microsoft APM action installs the packages and packs them into a
`.tar.gz` bundle, which is uploaded as the `apm` workflow artifact.
2. **Restore** (agent job): The bundle is downloaded and unpacked into the agent environment
before the AI model runs, providing it with all configured skills and tools.

### Package format

Packages use the format `owner/repo` or `owner/repo/path/to/skill`:
- `microsoft/apm-sample-package` — organization/repository
- `github/awesome-copilot/skills/review-and-refactor` — organization/repository/path

### Authentication

By default, packages are fetched using the cascading token fallback:
`GH_AW_PLUGINS_TOKEN || GH_AW_GITHUB_TOKEN || GITHUB_TOKEN`

For private packages requiring a specific token, use `dependencies.github-token` directly
in your main workflow frontmatter:

```yaml
dependencies:
packages:
- myorg/private-skills
github-token: YOUR_GITHUB_TOKEN_SECRET
```

For cross-organization private packages using a GitHub App, configure `dependencies.github-app`
in your main workflow frontmatter.
-->
113 changes: 58 additions & 55 deletions .github/workflows/smoke-claude.lock.yml

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions .github/workflows/smoke-claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ imports:
- shared/go-make.md
- shared/github-mcp-app.md
- shared/mcp/serena-go.md
- uses: shared/apm.md
with:
packages:
- microsoft/apm-sample-package
network:
allowed:
- defaults
Expand All @@ -44,9 +48,6 @@ tools:
edit:
bash:
- "*"
dependencies:
packages:
- microsoft/apm-sample-package
runtimes:
go:
version: "1.25"
Expand Down
11 changes: 11 additions & 0 deletions pkg/parser/import_field_extractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type importAccumulator struct {
skipBotsSet map[string]bool
caches []string
features []map[string]any
dependenciesBuilder strings.Builder // JSON-serialized dependencies configs from imported workflows
agentFile string
agentImportSpec string
repositoryImports []string
Expand Down Expand Up @@ -339,6 +340,15 @@ func (acc *importAccumulator) extractAllImportFields(content []byte, item import
}
}

// Extract dependencies from imported file (append in order for later merging)
dependenciesContent, err := extractFieldJSONFromMap(fm, "dependencies", "")
if err != nil {
log.Printf("Failed to extract dependencies from import %s: %v", item.fullPath, err)
} else if dependenciesContent != "" {
acc.dependenciesBuilder.WriteString(dependenciesContent + "\n")
log.Printf("Extracted dependencies from import: %s", item.fullPath)
}

return nil
}

Expand Down Expand Up @@ -370,6 +380,7 @@ func (acc *importAccumulator) toImportsResult(topologicalOrder []string) *Import
MergedCaches: acc.caches,
MergedJobs: acc.jobsBuilder.String(),
MergedFeatures: acc.features,
MergedDependencies: acc.dependenciesBuilder.String(),
ImportedFiles: topologicalOrder,
AgentFile: acc.agentFile,
AgentImportSpec: acc.agentImportSpec,
Expand Down
1 change: 1 addition & 0 deletions pkg/parser/import_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type ImportsResult struct {
MergedCaches []string // Merged cache configurations from all imports (appended in order)
MergedJobs string // Merged jobs from imported YAML workflows (JSON format)
MergedFeatures []map[string]any // Merged features configuration from all imports (parsed YAML structures)
MergedDependencies string // Merged dependencies configuration from imported workflows (newline-separated JSON)
ImportedFiles []string // List of imported file paths (for manifest)
AgentFile string // Path to custom agent file (if imported)
AgentImportSpec string // Original import specification for agent file (e.g., "owner/repo/path@ref")
Expand Down
1 change: 1 addition & 0 deletions pkg/parser/include_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ func processIncludedFileWithVisited(filePath, sectionName string, extractTools b
"infer": true, // Custom agent format field (Copilot) - deprecated, use disable-model-invocation
"disable-model-invocation": true, // Custom agent format field (Copilot)
"features": true,
"dependencies": true, // APM (Agent Package Manager) dependency packages for shared workflows
}

// Check for unexpected frontmatter fields
Expand Down
33 changes: 33 additions & 0 deletions pkg/workflow/compiler_orchestrator_tools.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package workflow

import (
"encoding/json"
"fmt"
"os"
"sort"
Expand Down Expand Up @@ -165,6 +166,38 @@ func (c *Compiler) processToolsAndMarkdown(result *parser.FrontmatterResult, cle
orchestratorToolsLog.Printf("Extracted %d APM dependencies from frontmatter", len(apmDependencies.Packages))
}

// Merge APM dependencies from imported shared workflows (e.g. shared/apm.md)
if importsResult.MergedDependencies != "" {
for line := range strings.SplitSeq(strings.TrimSpace(importsResult.MergedDependencies), "\n") {
line = strings.TrimSpace(line)
if line == "" || line == "null" {
continue
}
var depValue any
if jsonErr := json.Unmarshal([]byte(line), &depValue); jsonErr != nil {
orchestratorToolsLog.Printf("Failed to parse imported dependencies JSON: %v", jsonErr)
continue
}
importedDeps, depErr := extractAPMDependenciesFromValue(depValue, "imported:dependencies")
if depErr != nil || importedDeps == nil {
continue
}
orchestratorToolsLog.Printf("Merging %d APM packages from imported dependencies", len(importedDeps.Packages))
if apmDependencies == nil {
apmDependencies = importedDeps
} else {
// Append packages from imports; main workflow auth config takes precedence
apmDependencies.Packages = append(apmDependencies.Packages, importedDeps.Packages...)
if apmDependencies.GitHubToken == "" && importedDeps.GitHubToken != "" {
apmDependencies.GitHubToken = importedDeps.GitHubToken
}
if apmDependencies.GitHubApp == nil && importedDeps.GitHubApp != nil {
apmDependencies.GitHubApp = importedDeps.GitHubApp
}
}
}
}

// Add MCP fetch server if needed (when web-fetch is requested but engine doesn't support it)
tools, _ = AddMCPFetchServerIfNeeded(tools, agenticEngine)

Expand Down