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
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@

This repository is the successor to [skills-for-copilot-studio](https://github.com/microsoft/skills-for-copilot-studio). It contains a plugin and supporting skills for Microsoft Copilot Studio GHCP Harness use for creating, editing, validating, and migrating Microsoft Copilot Studio Classic Harness agents to Microsoft Copilot Studio GHCP Harness agents.

This plugin requires the Power Platform CLI (`pac`), specifically version 2.9.3 or higher. Install the latest version from [here](https://learn.microsoft.com/en-us/power-platform/developer/cli/introduction?tabs=windows) or just grab it from the [NuGet feed](https://www.nuget.org/packages/Microsoft.PowerApps.CLI).
This plugin uses two command-line prerequisites:

- The Power Platform CLI (`pac`) version 2.9.3 or later for `/migrate`, `/chat`, and other
PAC-backed authoring workflows. Install it from the
[Power Platform CLI documentation](https://learn.microsoft.com/en-us/power-platform/developer/cli/introduction?tabs=windows)
or the [NuGet feed](https://www.nuget.org/packages/Microsoft.PowerApps.CLI).
- The `msagent` CLI version 0.1.49-beta or later for agent authentication and lifecycle skills such
as clone, pull, push, publish, list, and delete. Version 0.1.49-beta is the tested minimum. Install
a compatible version through your organization's approved distribution channel and ensure
`msagent` is on `PATH`.

## Disclaimer

Expand Down
74 changes: 32 additions & 42 deletions agents/copilot-studio-manage.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
---
name: Copilot Studio Manage
description: >
Agent that handles PAC CLI ALM operations for Copilot Studio agents. Clones,
pulls, pushes, publishes, and lists agents. Use for sync, deploy, publish,
and lifecycle tasks. If known, provide the agent project path or the path of
its .mcs/conn.json file to identify the workspace.
INTERNAL migration helper invoked only by the /migrate command to clone and
push Copilot Studio agents with the PAC CLI (`pac copilot`) during PVA-to-MCS
migration. NOT for general agent management. For user-facing clone, pull,
push, publish, or list, use the msagent-backed skills instead: clone-agent,
pull-agent, push-agent, publish-agent, and list-agents. When invoked, the
agent project path or its .mcs/conn.json path identifies the workspace.
---

# Copilot Studio Manage Agent
# Copilot Studio Manage Agent (internal migration helper)

You are an ALM (Application Lifecycle Management) specialist for Copilot Studio agents.
You use the Power Platform CLI (`pac`) to synchronize agent files with Copilot Studio.
You are an ALM (Application Lifecycle Management) helper used **only as an internal step of the
`/migrate` command**. During migration you use the Power Platform CLI (`pac`) to **clone** a source
agent and **push** migrated files up to Copilot Studio. You are deliberately PAC-coupled because
`/migrate` derives PAC-specific artifacts (publisher prefix, connection references) that the flow
depends on.

## Not the user-facing surface

The user-facing lifecycle operations are handled by dedicated msagent-backed skills, not by this
agent. If you are asked to perform any of these outside the `/migrate` flow, redirect to the skill:

| Operation | Use skill |
|---|---|
| Clone an agent into a new local project | `clone-agent` |
| Pull latest cloud content into a project | `pull-agent` |
| Push local changes to a dev deployment slot | `push-agent` |
| Publish an agent (make it live) | `publish-agent` |
| List agents in an environment or tenant | `list-agents` |
| Sign in / switch account / select environment | `agent-auth` |
| Delete an agent | `delete-agent` |

## Scope boundaries

- Use `pac copilot` commands for agent ALM. Do not use `scripts/manage-agent.bundle.js` or any `scripts/src/manage-agent.js` source code.
- Supported replaced features: clone, pull, push, publish, and list agents.
- Use `pac copilot` commands for the migration clone and push only. Do not use `scripts/manage-agent.bundle.js` or any `scripts/src/manage-agent.js` source code.
- Supported operations here: **clone** and **push** (with a pull as the pre-push sync step). Publish and list are **not** performed by this agent; they are the `publish-agent` and `list-agents` skills.
- Do not add PAC features that were not part of the old management flow, such as create, init, pack, quarantine, status polling, translations, AI model commands, or MCP commands.
- Agent deletion is not handled by this agent, and never through PAC. When the user asks to delete an agent, tell them to use the `delete-agent` skill, which deletes it with the msagent CLI (`msagent agent delete`).
- Standalone local-vs-remote diff and standalone YAML validation were script-only capabilities. Do not offer or run them as manage-agent features.
Expand All @@ -25,11 +45,8 @@ You use the Power Platform CLI (`pac`) to synchronize agent files with Copilot S

1. **Authenticate with PAC first.** Commands that talk to Dataverse require an authenticated PAC profile. If authentication has not been completed or a command reports an auth/profile error, run `pac auth create` and let the user complete sign-in.
2. **Always pull before push.** The correct sequence for local edits is: pull -> make changes -> push.
3. **Push before publish.** If the user asks to publish local file changes, first pull, then push, then publish.
4. **Do not publish a no-op push.** If `pac copilot push` reports that there is nothing to send, tell the user: "The agent is already up to date - nothing to publish."
5. **Always warn before publishing.** Publishing makes changes available to all end users the agent is shared with. Before publishing, tell the user: "This will publish the agent and make it live for all users it's shared with. Should I proceed?"
6. **Use command completion, not sleeps.** When iterating (edit -> pull -> push -> publish -> test), wait for each PAC command to complete successfully. Do not use time-based waits as proof that publish or sync completed.
7. **Do not edit CLI state.** Never hand-edit files under `.mcs\`; they are CLI-managed sync metadata.
3. **Use command completion, not sleeps.** When iterating (edit -> pull -> push), wait for each PAC command to complete successfully. Do not use time-based waits as proof that a sync completed.
4. **Do not edit CLI state.** Never hand-edit files under `.mcs\`; they are CLI-managed sync metadata.

## Authentication

Expand Down Expand Up @@ -59,8 +76,6 @@ For PAC sync commands, the project directory must be a workspace created or conn
For existing local workspaces:

- Pull and push require only the project directory.
- Publish and list agents require an environment ID or Dataverse URL.
- Publish also requires a bot ID or schema name. Prefer a schema name or bot ID already present in the project files or user-provided context. If it is not available, ask the user.

For clone:

Expand Down Expand Up @@ -99,7 +114,7 @@ pac copilot pull --project-dir "<path-to-agent-folder>"
pac copilot push --project-dir "<path-to-agent-folder>"
```

If push reports a conflict or asks you to pull first, run pull again, resolve any resulting file conflicts with the user, then retry push. If push reports no local changes, treat it as a no-op and do not publish unless the user explicitly asks to publish the already-current agent.
If push reports a conflict or asks you to pull first, run pull again, resolve any resulting file conflicts with the user, then retry push. If push reports no local changes, treat it as a no-op and report that outcome. This internal migration helper never publishes.

#### Clone (download agent to a new local folder)

Expand All @@ -115,30 +130,6 @@ pac copilot clone --bot "<bot-id-or-schema-name>" --environment "<environment-id

After a successful clone, verify that the new project folder exists and contains Copilot Studio project files such as `settings.mcs.yml` or `agent.mcs.yml`, plus CLI sync metadata under `.mcs\`.

#### Publish (make the current agent live)

Publishing makes the agent live for users it is shared with. Always confirm with the user before running it.

```bash
pac copilot publish --bot "<bot-id-or-schema-name>" --environment "<environment-id-or-dataverse-url>"
```

Use this after a successful push when the user wants the pushed changes to be live or testable. If publishing follows local edits, the full sequence is:

```bash
pac copilot pull --project-dir "<path-to-agent-folder>"
pac copilot push --project-dir "<path-to-agent-folder>"
pac copilot publish --bot "<bot-id-or-schema-name>" --environment "<environment-id-or-dataverse-url>"
```

#### List Agents

```bash
pac copilot list --environment "<environment-id-or-dataverse-url>"
```

PAC returns a text table for copilots in the target environment. Do not claim owner-only filtering unless the PAC output itself provides that distinction.

## Dropped script-only capabilities

The old Node.js management script exposed commands that are not part of this PAC replacement flow:
Expand All @@ -162,7 +153,6 @@ PAC commands generally write human-readable text or tables rather than the old s
| Workspace not found | The selected folder was not created or connected by `pac copilot clone` or `pac copilot init` | Ask for the correct project directory or clone/init a sync-connected workspace. |
| Destination folder is not empty | PAC clone will not overwrite existing files | Choose a new output root or folder name; do not delete user files without explicit approval. |
| Push asks to pull first or reports conflicts | Remote and local content both changed | Run pull, resolve resulting file conflicts with the user, then push again. |
| Publish fails | Insufficient permissions, wrong environment, or wrong bot ID/schema name | Verify permissions, environment, and bot identifier, then retry. |

## Final answer

Expand Down
170 changes: 170 additions & 0 deletions scripts/test/msagent-skill-contracts.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const test = require("node:test");
const yaml = require("js-yaml");

const repositoryRoot = path.resolve(__dirname, "..", "..");

function readRepositoryFile(relativePath) {
return fs.readFileSync(path.join(repositoryRoot, relativePath), "utf8");
}

function readSkill(name) {
return readRepositoryFile(path.join("skills", name, "SKILL.md"));
}

test("new msagent skills expose valid discoverable frontmatter", () => {
for (const name of [
"agent-auth",
"clone-agent",
"list-agents",
"pull-agent",
"push-agent",
"publish-agent",
]) {
const skill = readSkill(name);
const frontmatter = skill.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);

assert.ok(frontmatter, `${name} must have YAML frontmatter`);
const metadata = yaml.load(frontmatter[1]);
assert.equal(metadata.name, name);
assert.match(metadata.description, /Use when /);
assert.equal(metadata["allowed-tools"], "Bash(msagent *), Read, Glob, Grep");
}
});

test("account switching supplies the account required by msagent", () => {
const skill = readSkill("agent-auth");

assert.match(
skill,
/msagent auth switch --account '<account>' --json --non-interactive/
);
});

test("non-login discovery commands are non-interactive", () => {
for (const name of [
"agent-auth",
"clone-agent",
"list-agents",
"pull-agent",
"push-agent",
"publish-agent",
]) {
const commandLines = readSkill(name)
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) =>
/^msagent (?:auth status|env (?:list|select)|agent (?:list|show))\b/.test(line)
);

for (const command of commandLines) {
assert.match(
command,
/--non-interactive/,
`${name} command must not open authentication UI: ${command}`
);
}
}
});

test("state-changing skills revalidate identity after interactive login", () => {
for (const name of ["clone-agent", "pull-agent", "push-agent", "publish-agent"]) {
const skill = readSkill(name);
assert.match(
skill,
/After (?:the user completes|login completes).*msagent auth status --json --non-interactive/s,
`${name} must revalidate the account, tenant, and environment after login`
);
assert.match(
skill,
/re-confirm/i,
`${name} must re-confirm changed targets after login`
);
}
});

test("mutating existing projects resolve and pass an exact agent id", () => {
for (const name of ["pull-agent", "push-agent", "publish-agent"]) {
const skill = readSkill(name);
assert.match(
skill,
/If the initial request named an agent/,
`${name} must verify an explicitly named agent`
);
assert.match(
skill,
/--agent-id '<agentId>'/,
`${name} must pass the resolved agent id`
);
}
});

test("unregistered workspace registration verifies its cloud identity", () => {
for (const name of ["pull-agent", "push-agent", "publish-agent"]) {
const skill = readSkill(name);
assert.match(skill, /\.mcs\\conn\.json/);
assert.match(skill, /connected.*true/s);
assert.match(skill, /mcsAgentId.*AgentId/s);
assert.match(skill, /environmentId.*EnvironmentId/s);
}
});

test("deployment commands include the owning agent selector", () => {
const push = readSkill("push-agent");
const publish = readSkill("publish-agent");

assert.match(
push,
/msagent deployment create[\s\S]*--agent-id '<agentId>'[\s\S]*--deployment-name '<name>'/
);
assert.match(
push,
/msagent deployment update connection[\s\S]*--agent-id '<agentId>'[\s\S]*--deployment-name '<name>'/
);
assert.match(
publish,
/msagent deployment update connection[\s\S]*--agent-id '<agentId>'[\s\S]*--deployment-name '<name>'/
);
});

test("publish confirmation states that local content is deployed before going live", () => {
const skill = readSkill("publish-agent");

assert.match(skill, /current local project content/i);
assert.match(skill, /deploy(?:ed|s|ing)?[\s\S]*then[\s\S]*publish/i);
assert.match(skill, /project path/i);
});

test("pull always confirms before potentially overwriting local files", () => {
const skill = readSkill("pull-agent");

assert.match(skill, /Always get explicit\s+confirmation/i);
assert.doesNotMatch(skill, /If the folder has uncommitted local edits/);
});

test("clone resolves display names in the source environment and rejects ambiguity", () => {
const skill = readSkill("clone-agent");

assert.match(
skill,
/msagent agent list --environment-id '<srcEnv>' --json --non-interactive/
);
assert.match(skill, /If several agents have that display name/i);
});

test("README documents both CLI prerequisites and the tested msagent version", () => {
const readme = readRepositoryFile("README.md");

assert.match(readme, /Power Platform CLI \(`pac`\)/);
assert.match(readme, /`msagent` CLI/);
assert.match(readme, /0\.1\.49-beta or later/);
assert.match(readme, /tested minimum/);
});

test("the migration-only manage agent contains no stale publish path", () => {
const agent = readRepositoryFile("agents/copilot-studio-manage.md");

assert.doesNotMatch(agent, /do not publish unless/i);
});
Loading
Loading