Skip to content

Add azd ai agent pack and publish for Teams (activity) agents - #9332

Open
JianW (v1212) wants to merge 25 commits into
Azure:mainfrom
v1212:users/wujia/activity-teams-pack-api
Open

Add azd ai agent pack and publish for Teams (activity) agents#9332
JianW (v1212) wants to merge 25 commits into
Azure:mainfrom
v1212:users/wujia/activity-teams-pack-api

Conversation

@v1212

@v1212 JianW (v1212) commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Fixes #9400
Related to #6752

Summary

Adds two explicit commands for Activity (Teams) agents. Both require a prior azd deploy (the Azure Bot they bind to is created then) and surface platform failures (tenant policy, permissions, service errors) as command failures rather than silent fallbacks.

Design: two independent commands

azd ai agent pack

Downloads a ready-to-sideload Teams app package (appPackage.zip) from the Foundry Microsoft 365 zip endpoint. Intended for the developer inner loop: customize the package and/or sideload it for local testing with atk install --scope personal (no Teams admin required).

  • Flags: [name] --scope --display-name --app-version --output-dir
  • Default scope: personal
  • Supported scopes mirror the service contract: personal, shared, tenant (org is accepted as a compatibility alias for tenant).
  • Writes appPackage.zip next to the agent source (or --output-dir).

azd ai agent publish

Builds the same package server-side and publishes it to the Microsoft 365 store, returning the title id, Teams app id, and install deep link.

  • Flags: [name] --scope --display-name --app-version --output
  • Default scope: shared
  • shared — shareable install link; recipients can add the app without tenant-admin approval.
  • tenant — organization-wide catalog; requires IT-admin approval before tenant users can use it in Teams.
  • personal is not supported — per-user install is a Teams client action, not a store publish. For that path use azd ai agent pack + atk install --scope personal.
  • --output json returns pure JSON with titleId, teamsAppId, scope, and deepLink.

Failure transparency

Unlike deploy-time best-effort packaging, these commands report backend errors faithfully. If the platform fails (for example, the Microsoft 365 /publish endpoint errors), azd ai agent publish fails with the underlying service error instead of silently skipping.

Testing

  • go test ./internal/cmd ./internal/pkg/agents/agent_api ./internal/exterrors
  • go fix -diff ./... reports no remaining suggestions.
  • Built/installed the local extension via azd x pack --rebuild, azd x publish --source local, and azd extension install azure.ai.agents --source local --force.
  • Validated azd ai agent pack echopub073001 --scope personal --output-dir <tmp>:
    • Produced appPackage.zip.
    • Zip contains manifest.json, default-color-icon.png, and default-outline-icon.png.
    • Manifest has name.short=echopub073001, version=1.0.0, developer.name=Azure AI Foundry, and botId=119aa5ee-f7db-41ce-a50c-9853b54133a5.
  • Validated azd ai agent publish echopub073001 --scope shared --no-prompt:
    • Returned titleId=T_7921a0e5-c58b-8dbc-d833-ed5ef671eec4.
    • Returned teamsAppId=040abe71-46ba-44ad-a7f1-8d7bdf426062.
    • Returned install link https://teams.microsoft.com/l/app/040abe71-46ba-44ad-a7f1-8d7bdf426062.
  • Validated default publish scope: azd ai agent publish echopub073001 --no-prompt publishes with scope: shared.
  • Validated azd ai agent publish echopub073001 --no-prompt -o json returns parseable JSON.
  • Validated azd ai agent publish echopub073001 --scope personal --no-prompt fails with an actionable message pointing to pack + atk install --scope personal.
  • Removed the prior personal sideload install, opened the shared install in Teams Web, sent hi, and received Echo : hi from echopub073001.

Latest E2E validation

Validated with the PR-built local azure.ai.agents extension (1.0.0-beta.8, commit c55a3335f) against a fresh Activity echo project:

  • azd ai agent init from the Activity quickstart echo sample
  • azd provision (new Foundry project + ACR)
  • azd deploy (agent echopr933208032120, active version 3)
  • azd ai agent publish --scope shared --display-name echopr933208032120
  • Teams Web: published shared app was discoverable/installable without admin approval; hi received Echo: hi

Publish result used for validation:

{
  "titleId": "T_8a6472dd-f4d3-4337-4be1-09da737a5cc2",
  "teamsAppId": "788aac2c-d433-4f94-84ac-97f68d3ed452"
}

For activity-protocol agents, `azd deploy` postdeploy now calls the Foundry
Microsoft 365 packaging API (`/agents/{name}/microsoft365/zip`) to download a
ready-to-sideload Teams app package (`appPackage.zip`) next to the agent source,
right after it creates the Azure Bot + Teams channel. The service builds the
manifest, icons, and bot entry (botId = agent instance identity), so users no
longer assemble a Teams manifest by hand.

`TEAMS_APP_SETUP.md` now leads with sideloading the generated package (Teams UI
or `atk --scope Personal`, no Teams admin needed) and only falls back to the
manual packaging steps when package generation fails.

Additive and best-effort: packaging failures are logged and never fail the
deploy, and non-activity agents are completely unaffected. Publishing to the org
catalog (Shared/Tenant, admin-gated) is intentionally out of scope.

- Add agent_api.DownloadTeamsAppPackage + TeamsAppPackageRequest (+ tests)
- Add botservice.BotArmID helper (+ test)
- Wire writeTeamsAppPackage into ensureActivityBot; thread the package into the
  guide/next-steps (+ updated tests)
- Rewrite the embedded guide with a generated-package vs manual fallback branch

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
21 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds best-effort generation of a sideloadable Teams app package after deploying activity-protocol agents.

Changes:

  • Adds Microsoft 365 package download and Azure Bot ARM ID helpers.
  • Writes appPackage.zip and updates Teams setup guidance.
  • Adds unit tests and release notes.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
internal/pkg/botservice/botservice.go Builds Azure Bot ARM IDs.
internal/pkg/botservice/botservice_test.go Tests ARM ID generation.
internal/pkg/agents/agent_api/microsoft365.go Calls the Microsoft 365 packaging API.
internal/pkg/agents/agent_api/microsoft365_test.go Tests packaging API requests and errors.
internal/cmd/listen_activity.go Generates packages and presents next steps.
internal/cmd/listen_activity_test.go Tests generated and fallback guidance.
internal/cmd/assets/teams_app_setup_guide.md Documents sideloading and manual fallback.
CHANGELOG.md Adds an unreleased feature entry.

Comment thread cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/microsoft365.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go
Comment thread cli/azd/extensions/azure.ai.agents/CHANGELOG.md Outdated
@github-actions github-actions Bot added the ext-agents azure.ai.agents extension label Jul 28, 2026
…tion

The postdeploy hook now downloads a ready-to-sideload Teams app package, so the doc comment claiming packaging stays out of azd was stale.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Copilot AI review requested due to automatic review settings July 28, 2026 09:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (5)

cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/microsoft365.go:103

  • [azd-code-reviewer] Route this finite request through AgentClient.pipeline instead of a fresh http.Client. NewAgentClient configures bearer auth, Azure SDK retries/logging, correlation IDs, and the user agent in operations.go:61-84; bypassing it means transient 429/5xx responses immediately force the manual fallback and this call lacks the correlation behavior used by the other agent APIs. Build a runtime request, set its JSON body and feature header, and call c.pipeline.Do.
	httpClient := &http.Client{}
	resp, err := httpClient.Do(req)

cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:275

  • [azd-code-reviewer] If the package write succeeds but the guide write fails, this prints a ready package and then the guidePath == "" branch tells the user to package the app manually. Branch on packagePath there so this partial-success case gives sideloading instructions rather than contradictory guidance.
	if packagePath != "" {
		fmt.Printf("  Teams app:  %s (ready to sideload)\n", packagePath)

cli/azd/extensions/azure.ai.agents/CHANGELOG.md:7

  • [azd-code-reviewer] Remove this entry from the feature PR. The extension's release workflow reserves CHANGELOG.md updates for the dedicated version-bump PR that also changes version.txt and extension.yaml (cli/azd/extensions/azure.ai.agents/AGENTS.md:123-139); adding it here creates release-time merge conflicts and separates the entry from its version bump.
## 1.0.0-beta.8 (Unreleased)

### Features Added

- [[#9332]](https://github.com/Azure/azure-dev/pull/9332) For activity-protocol agents, `azd deploy` now generates a ready-to-sideload Teams app package (`appPackage.zip`) next to the agent source by calling the Foundry Microsoft 365 packaging API, so you no longer have to assemble a Teams manifest and icons by hand. `TEAMS_APP_SETUP.md` now leads with sideloading that package (Teams UI or the `atk` CLI, `--scope Personal`, no Teams admin needed) and only falls back to manual packaging steps if package generation fails. Non-activity agents are unaffected.

cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:169

  • [azd-code-reviewer] writeTeamsAppPackage is the central integration path but has no test. The lower-level API and ARM-ID tests do not verify the destination path, request metadata, successful file write, or the promised empty-string fallback on API/write failures. Add temp-directory and httptest coverage, consistent with the existing writeTeamsSetupGuide test in this package.
func writeTeamsAppPackage(
	ctx context.Context,
	agentClient *agent_api.AgentClient,
	proj *azdext.ProjectConfig,
	svc *azdext.ServiceConfig,
	agentName, subscriptionID, resourceGroup, botName string,
) string {

cli/azd/extensions/azure.ai.agents/internal/cmd/assets/teams_app_setup_guide.md:29

  • [azd-code-reviewer] This copy-paste command resolves appPackage.zip relative to the user's current shell directory, but azd deploy normally leaves the shell at the project root while the package may be under the service's project: path. For services such as src/agent, the command fails unless the user changes directory. State that these commands must run from the directory containing this guide/package, or render a quoted path that works from the project root.
atk install --file-path {{.PackageFile}} --scope Personal

- Route the Microsoft 365 pack call through the shared client pipeline (retry/
  correlation/bearer policies) instead of a bare http.Client, so transient
  429/5xx are retried rather than dropping straight to the manual fallback.
- Write the package atomically (temp file + rename) and remove any stale
  appPackage.zip on fallback, so a failed run can't leave a partial or
  mismatched zip that the guide would point users at.
- When the guide write fails but the package exists, direct users to sideload
  the generated package instead of packaging manually.
- Drop the CHANGELOG entry; changelog updates are deferred to the release PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Copilot AI review requested due to automatic review settings July 28, 2026 09:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:218

  • This reimplements the repository's atomic writer with a shared fixed .tmp name and direct os.Rename, missing the unique temp file, fsync, cleanup, and Windows transient-lock retries provided by azdext.WriteFileAtomic (cli/azd/pkg/azdext/atomicfile.go:20-94). Reuse that helper so simultaneous writers cannot collide and a transient Windows file lock does not unnecessarily force the manual fallback. [azd-code-reviewer]
	// Write atomically (temp file + rename) so an interrupted or failed write can
	// never leave a partial/corrupt zip that the guide would point users at.
	tmpPath := packagePath + ".tmp"
	if err := os.WriteFile(tmpPath, zipBytes, 0o600); err != nil {

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go Outdated
The generated appPackage.zip lives in the user's source dir under a generic
name, so a user may keep their own manually assembled package there. Track
azd ownership with a sidecar marker (.appPackage.zip.azd-generated): azd now
only overwrites or removes the package when the marker is present, preserving
an unowned user file on both the success and fallback paths. Add regression
tests covering preserve-unowned, write-when-absent, overwrite-owned, and
remove-owned.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Copilot AI review requested due to automatic review settings July 28, 2026 10:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (6)

cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:214

  • [azd-code-reviewer] Local commit failures skip the stale-package cleanup used for API failures. If an owned package from a previous deploy exists and the temp write or rename fails, that stale ZIP and marker remain even though the guide switches to manual fallback; its bot binding may no longer match this deployment. Remove the owned artifact before returning here.
	if err != nil {
		log.Printf("postdeploy: failed to write Teams app package %q: %v", packagePath, err)
		return ""

cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:241

  • [azd-code-reviewer] The predictable .tmp path bypasses the project path's symlink validation. A checked-out repository can contain appPackage.zip.tmp as a symlink outside the project, and os.WriteFile follows it and truncates the target during deploy. Create a unique temp file in the package directory, close it, and rename it instead.
	tmpPath := packagePath + ".tmp"
	if err := os.WriteFile(tmpPath, zipBytes, 0o600); err != nil {

cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:251

  • [azd-code-reviewer] os.WriteFile follows a pre-existing marker symlink. Because markerPath is not validated when the package is absent, a repository containing .appPackage.zip.azd-generated as a symlink can make a successful deploy truncate a file outside the project; teamsAppPackageIsOwned also trusts that symlink via os.Stat. Write the marker through a same-directory temp file plus rename, and use os.Lstat to reject non-regular ownership markers.
	if err := os.WriteFile(markerPath, []byte("generated by azd\n"), 0o600); err != nil {

cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:156

  • [azd-code-reviewer] appPackage.zip is scoped only by the service directory, but this extension supports multiple agent services sharing the same RelativePath (for example, helpers_test.go:880-881), and service deploy steps can run concurrently (cli/azd/AGENTS.md:214-220). Those agents race on the same package and marker, so the final ZIP can belong to a different bot than an earlier service's output claims. Namespace the package and marker by service/agent, or use a per-service output directory, and cover a shared-root multi-agent deploy.
const teamsAppPackageFile = "appPackage.zip"

cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:271

  • [azd-code-reviewer] If deleting the stale package fails, this still removes its ownership marker. The stale ZIP then becomes permanently "unowned", so later deploys refuse to replace it and keep falling back to manual packaging. Preserve the marker when package deletion fails so a future run can retry cleanup.
	if err := os.Remove(packagePath); err != nil && !errors.Is(err, os.ErrNotExist) {
		log.Printf("postdeploy: could not remove stale Teams app package %q: %v", packagePath, err)
	}
	if err := os.Remove(markerPath); err != nil && !errors.Is(err, os.ErrNotExist) {
		log.Printf("postdeploy: could not remove Teams app package marker %q: %v", markerPath, err)

cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:202

  • [azd-code-reviewer] This treats cancellation of the parent deploy context as an ordinary packaging failure. If the user cancels during this network call, the hook continues writing fallback files and returns success instead of stopping. Keep the 60-second packaging timeout best-effort, but when the parent ctx.Err() is non-nil, propagate that cancellation through ensureActivityBot.
	if err != nil {

@v1212
JianW (v1212) marked this pull request as draft July 30, 2026 06:26
Add two explicit, loud commands for Activity (Teams) agents that complement
the deploy-time best-effort packaging:

- `azd ai agent pack` downloads a ready-to-sideload Teams app package
  (appPackage.zip) from the Foundry Microsoft 365 zip endpoint for local
  customization and `atk install --scope personal` sideloading.
- `azd ai agent publish` builds and publishes the package to the Microsoft
  365 store. Defaults to `shared` scope (shareable install link, no
  tenant-admin approval) and supports `org` scope (organization catalog,
  admin-gated). `personal` is rejected with guidance to use pack + sideload,
  since per-user install is a Teams client action, not a store publish.

Both require a prior `azd deploy` (the Azure Bot they bind to is created then)
and are activity-agent only. Unlike the postdeploy hook, platform failures
(tenant policy, permissions, service errors) surface as command failures
instead of silent fallbacks.

- Add agent_api.PublishTeamsApp + TeamsAppPublishResult
- Add shared resolveTeamsPackContext / scope + request helpers (+ tests)
- Register pack/publish in root; add exterrors codes/ops

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
@v1212 JianW (v1212) changed the title Generate ready-to-sideload Teams app package on activity-protocol deploy Teams app packaging for activity agents: deploy-time package + pack/publish commands Jul 30, 2026

@jongio Jon Gallant (jongio) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rechecked after the latest push. The two items from my last pass are genuinely fixed, and build plus tests are green.

Two notes on the new --agent-name adopt override and one carried over on the always-excluded list. All inline, none blocking.

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init_adopt.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init_adopt.go
Comment thread cli/azd/extensions/azure.ai.agents/internal/project/agentignore.go Outdated
Make adopted azure.yaml agent-name guidance actionable for single- and multi-agent samples, fail when --agent-name cannot be applied to a named agent service, preserve .agentignore negation semantics for generated Teams artifacts, and clarify pack/publish output by showing the Teams app display name separately from the Foundry agent name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Copilot AI review requested due to automatic review settings August 5, 2026 03:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

cli/azd/extensions/azure.ai.agents/internal/cmd/publish.go:159

  • The new machine-readable output has no automated command-level coverage, so regressions that add progress text or omit/misformat titleId, teamsAppId, scope, or deepLink can ship unnoticed. Add a test that captures stdout, parses it as JSON, and asserts these fields; this repository explicitly requires tests for new JSON output fields. [azd-code-reviewer]
	if a.flags.output == "json" {
		payload := map[string]string{
			"titleId":     result.TitleID,
			"teamsAppId":  result.TeamsAppID,
			"scope":       scope.flag,
			"displayName": displayName,
			"deepLink":    deepLink,
		}

cli/azd/extensions/azure.ai.agents/internal/cmd/publish.go:100

  • For an invalid value such as --scope everyone, this generic resolver tells publish users to choose personal, but validatePublishScope immediately rejects personal. Validate publish input against teamsPublishScopes (or otherwise customize this error) so the suggested values are actually accepted by this command. [azd-code-reviewer]

This issue also appears on line 152 of the same file.

	scope, err := resolveTeamsPackScope(a.flags.scope)

cli/azd/extensions/azure.ai.agents/internal/cmd/teams_pack.go:106

  • The positional value is matched only against the azure.yaml service name (resolveAgentService checks s.Name), although both commands advertise [name] as an agent name. This fails for the newly supported init override where the user chooses agent test0804 but the service key remains agent-service: publish test0804 reports no matching service. Either resolve both service/deployed agent names or document the argument consistently as the azure.yaml service name. [azd-code-reviewer]
	svc, proj, err := resolveAgentService(ctx, azdClient, name, noPrompt)

cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/microsoft365.go:170

  • The publish contract returns both IDs and the command reports teamsAppId, but a 200 response missing teamsAppId is currently treated as success and emits an empty identifier. Reject that incomplete response just as titleId is rejected, and add the corresponding missing-field test case. [azd-code-reviewer]
	if result.TitleID == "" {
		return nil, fmt.Errorf("Teams app publish response was missing titleId")
	}
	return &result, nil

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go Outdated
Record the generated appPackage.zip SHA-256 in the ownership marker and only overwrite or remove the package when the current ZIP still matches that digest. Customized generated packages are now treated as user-owned and preserved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Copilot AI review requested due to automatic review settings August 5, 2026 03:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (5)

cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:290

  • When an owned package already existed, this failure path leaves the newly overwritten ZIP paired with the old digest marker. Every later deploy/pack then treats that ZIP as user-owned and refuses to replace or remove it. Preserve the previous package bytes and restore them if the marker update fails, or use a transactional commit; add coverage for marker failure while replacing an existing owned package. [azd-code-reviewer]
	if err := writeTeamsAppFileAtomically(markerPath, []byte(teamsAppPackageMarkerContent(zipBytes))); err != nil {

cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/microsoft365.go:170

  • A 200 response containing titleId but no teamsAppId is currently reported as success, so both human and JSON output violate the command’s promised result with an empty Teams app ID. Validate TeamsAppID before returning, just as TitleID is validated. [azd-code-reviewer]
	if result.TitleID == "" {
		return nil, fmt.Errorf("Teams app publish response was missing titleId")
	}
	return &result, nil

cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:351

  • If deleting the owned ZIP fails, the marker is still deleted below. That strands the stale package without ownership metadata, so future runs preserve it as user-owned forever. Return after a package-removal failure and retain the marker for a later cleanup retry. [azd-code-reviewer]
	if err := os.Remove(packagePath); err != nil && !errors.Is(err, os.ErrNotExist) {
		log.Printf("postdeploy: could not remove stale Teams app package %q: %v", packagePath, err)
	}
	if err := os.Remove(markerPath); err != nil && !errors.Is(err, os.ErrNotExist) {
		log.Printf("postdeploy: could not remove Teams app package marker %q: %v", markerPath, err)

cli/azd/extensions/azure.ai.agents/internal/cmd/init_adopt.go:947

  • This deterministic validation runs only after scaffoldProject has written the adopted azure.yaml. The tested multi-agent/no-named-agent rejection paths therefore leave a partial project that immediately blocks a retry, and picker-selected unified templates now enter this path even without an explicit --agent-name because line 1444 populates the flag. Validate override eligibility before scaffolding (and distinguish an explicit override from an automatically resolved folder/default name), or clean up the newly created target on failure. [azd-code-reviewer]
	if agentNameOverride != "" {
		if err := applyAdoptedAgentNameOverride(ctx, azdClient, agentNameOverride); err != nil {
			return err
		}

cli/azd/extensions/azure.ai.agents/internal/cmd/publish.go:152

  • The new machine-readable output contract is not covered by a command/action test; current tests exercise only API decoding and the deep-link helper. Add a test that captures --output json, parses it, asserts titleId, teamsAppId, scope, and deepLink, and verifies that human progress text is absent. [azd-code-reviewer]
	if a.flags.output == "json" {

@jongio Jon Gallant (jongio) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two failure paths can desynchronize the generated package from its ownership marker, leaving azd unable to manage the file on later deploys.

🤖 agent jongio

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Copilot AI review requested due to automatic review settings August 6, 2026 07:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/microsoft365.go:169

  • [azd-code-reviewer] A successful response containing titleId but omitting teamsAppId currently passes validation, so publish reports success and emits an empty Teams App ID despite both IDs being part of the endpoint and CLI contract. Validate teamsAppId before returning and add the symmetric malformed-response test.
	if result.TitleID == "" {
		return nil, fmt.Errorf("Teams app publish response was missing titleId")
	}

cli/azd/extensions/azure.ai.agents/internal/cmd/init.go:1444

  • [azd-code-reviewer] This assignment makes every prompt/default result look like an explicit --agent-name override. runInitFromAzureYaml then calls applyAdoptedAgentNameOverride, which rejects multiple named agent services, so selecting a multi-agent unified template now fails even when the user never passed --agent-name; the existing adoption flow otherwise handles each agent service. Preserve whether the flag was explicitly supplied, or detect a single-agent template before applying the resolved name.
						flags.agentName = resolvedName

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/pack.go Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Copilot AI review requested due to automatic review settings August 6, 2026 09:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/microsoft365.go:169

  • [azd-code-reviewer] A successful response that contains titleId but omits teamsAppId is currently accepted, so the command exits successfully and emits an empty Teams app ID despite the endpoint contract returning both IDs. Validate TeamsAppID before returning the result and cover the missing-field response in the test table.
	if result.TitleID == "" {
		return nil, fmt.Errorf("Teams app publish response was missing titleId")
	}

cli/azd/extensions/azure.ai.agents/internal/cmd/assets/teams_app_setup_guide.md:7

  • [azd-code-reviewer] The left trim marker removes the newline after the Bot ID bullet when PackageFile is set, rendering the generated package bullet on the same Markdown line. Preserve the preceding newline and trim the action line's following newline instead.
{{- if .PackageFile}}

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init.go Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Copilot AI review requested due to automatic review settings August 6, 2026 09:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

cli/azd/extensions/azure.ai.agents/internal/cmd/publish.go:103

  • [azd-code-reviewer] This shared pack resolver makes an invalid publish scope suggest personal, shared, tenant, even though personal is rejected immediately below. Return publish-specific remediation (shared, tenant, or alias org) so users are not directed to another failing invocation.
	scope, err := resolveTeamsPackScope(a.flags.scope)
	if err != nil {
		return err
	}

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init_adopt.go
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Copilot AI review requested due to automatic review settings August 7, 2026 05:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

cli/azd/extensions/azure.ai.agents/internal/cmd/teams_pack.go:106

  • The positional [name] is documented as an agent name, but this resolves only ServiceConfig.Name. When the deployed name differs—the case handled below via AGENT_<SERVICE>_NAMEpack <deployed-agent-name> and publish <deployed-agent-name> fail before reading the environment. Either label this positional as service-name in both commands and examples, consistent with show.go:52-59, or resolve both identities. [azd-code-reviewer]
	svc, proj, err := resolveAgentService(ctx, azdClient, name, noPrompt)

cli/azd/extensions/azure.ai.agents/internal/cmd/publish.go:159

  • --output json adds a public schema and pure-stdout guarantee, but no publish command test parses captured output or asserts the emitted fields and absence of progress text. Add action/renderer coverage for titleId, teamsAppId, scope, displayName, and deepLink; cli/azd/AGENTS.md:378 explicitly requires tests for new JSON fields. [azd-code-reviewer]
		payload := map[string]string{
			"titleId":     result.TitleID,
			"teamsAppId":  result.TeamsAppID,
			"scope":       scope.flag,
			"displayName": displayName,
			"deepLink":    deepLink,
		}

cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:560

  • Using only the deployed name drops cleanup for the legacy service-name bot. If an existing Activity service changes its agent name, the bot created by the prior service-name behavior remains while down deletes only the new bot. Since DeleteBot treats 404 as success, attempt both unique service-name and deployed-name bot IDs. [azd-code-reviewer]
		} else if deployedName != "" {
			agentName = deployedName
		}

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Copilot AI review requested due to automatic review settings August 7, 2026 05:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

Suppressed comments (2)

cli/azd/extensions/azure.ai.agents/internal/cmd/publish.go:159

  • [azd-code-reviewer] The new machine-readable contract has no automated coverage: existing tests validate the API response and deep-link helper, but none execute or isolate this branch to assert pure JSON and the required titleId, teamsAppId, scope, and deepLink fields. Add command/action output coverage so changes to status printing or payload fields cannot silently break --output json.
	if a.flags.output == "json" {
		payload := map[string]string{
			"titleId":     result.TitleID,
			"teamsAppId":  result.TeamsAppID,
			"scope":       scope.flag,
			"displayName": displayName,
			"deepLink":    deepLink,
		}

cli/azd/extensions/azure.ai.agents/internal/cmd/init_adopt.go:1110

  • [azd-code-reviewer] This skips supported service-level $ref agent definitions because the raw adopted YAML has host and $ref, while kind/name live in the referenced file. A single referenced agent is therefore rejected as having no named service; with one inline plus one referenced agent, the multi-agent guard can instead miss the referenced service and apply the override to only one agent. Count all azure.ai.agent services and make applyAdoptedAgentNameOverride write a top-level name sibling for $ref services (the include resolver overlays siblings onto the referenced definition); add $ref cases to these tests.
		if adoptedAgentNameOverrideServiceName(svc) == "" {
			continue
		}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ext-agents azure.ai.agents extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[azure.ai.agents] Add Teams app pack and publish commands for activity agents

4 participants