Skip to content

Generate ready-to-sideload Teams app package on activity-protocol deploy - #9332

Open
v1212 wants to merge 5 commits into
Azure:mainfrom
v1212:users/wujia/activity-teams-pack-api
Open

Generate ready-to-sideload Teams app package on activity-protocol deploy#9332
v1212 wants to merge 5 commits into
Azure:mainfrom
v1212:users/wujia/activity-teams-pack-api

Conversation

@v1212

@v1212 v1212 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Closes #9333

Summary

For activity-protocol agents, azd deploy postdeploy now calls the Foundry Microsoft 365 packaging API (POST /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 upload, or atk install --scope Personal — no Teams admin needed) and only falls back to the manual packaging steps when package generation fails.

Scope / safety

  • Additive and best-effort: packaging failures are logged and never fail the deploy.
  • Non-activity agents are completely unaffected (the postdeploy hook early-returns for non-hosted/non-activity agents).
  • The generated appPackage.zip is marked with a sidecar file so azd only overwrites/removes packages it created and never clobbers a user's own file.
  • Publishing to the org catalog (Shared/Tenant, which is Teams-admin-gated) is intentionally out of scope — end users can't self-test it.

Changes

  • Add agent_api.DownloadTeamsAppPackage + TeamsAppPackageRequest (+ tests).
  • Add botservice.BotArmID helper (+ test).
  • Wire writeTeamsAppPackage into ensureActivityBot; thread the package into the setup guide / next-steps output (+ updated tests).
  • Add package-ownership tracking (sidecar marker) so azd never overwrites or deletes a user-authored package (+ regression tests).
  • Rewrite the embedded guide with a generated-package vs. manual-fallback branch.

Validation

  • Verified live against a real echo deployment: pack /zip -> HTTP 200 (valid manifest + icons + bot entry with scopes incl. personal/groupChat and supportsFiles: true); atk install --scope Personal sideload -> agent replied to "hi" in Teams.
  • go build + full extension test suite green; gofmt clean.

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
Copilot AI review requested due to automatic review settings July 28, 2026 09:12
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 {

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.

Generate a ready-to-sideload Teams app package during azd deploy for activity-protocol agents

3 participants