Add azd ai agent pack and publish for Teams (activity) agents - #9332
Conversation
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: 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. |
There was a problem hiding this comment.
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.zipand 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. |
…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
There was a problem hiding this comment.
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.pipelineinstead of a freshhttp.Client.NewAgentClientconfigures bearer auth, Azure SDK retries/logging, correlation IDs, and the user agent inoperations.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 callc.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 onpackagePaththere 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.mdupdates for the dedicated version-bump PR that also changesversion.txtandextension.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]
writeTeamsAppPackageis 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 andhttptestcoverage, consistent with the existingwriteTeamsSetupGuidetest 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.ziprelative to the user's current shell directory, butazd deploynormally leaves the shell at the project root while the package may be under the service'sproject:path. For services such assrc/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
There was a problem hiding this comment.
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
.tmpname and directos.Rename, missing the unique temp file, fsync, cleanup, and Windows transient-lock retries provided byazdext.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 {
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
There was a problem hiding this comment.
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
.tmppath bypasses the project path's symlink validation. A checked-out repository can containappPackage.zip.tmpas a symlink outside the project, andos.WriteFilefollows 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.WriteFilefollows a pre-existing marker symlink. BecausemarkerPathis not validated when the package is absent, a repository containing.appPackage.zip.azd-generatedas a symlink can make a successful deploy truncate a file outside the project;teamsAppPackageIsOwnedalso trusts that symlink viaos.Stat. Write the marker through a same-directory temp file plus rename, and useos.Lstatto 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.zipis scoped only by the service directory, but this extension supports multiple agent services sharing the sameRelativePath(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 throughensureActivityBot.
if err != nil {
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
There was a problem hiding this comment.
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 thoughpersonalis rejected immediately below. Return publish-specific remediation (shared,tenant, or aliasorg) so users are not directed to another failing invocation.
scope, err := resolveTeamsPackScope(a.flags.scope)
if err != nil {
return err
}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
There was a problem hiding this comment.
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 onlyServiceConfig.Name. When the deployed name differs—the case handled below viaAGENT_<SERVICE>_NAME—pack <deployed-agent-name>andpublish <deployed-agent-name>fail before reading the environment. Either label this positional asservice-namein both commands and examples, consistent withshow.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 jsonadds 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 fortitleId,teamsAppId,scope,displayName, anddeepLink;cli/azd/AGENTS.md:378explicitly 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
downdeletes only the new bot. SinceDeleteBottreats 404 as success, attempt both unique service-name and deployed-name bot IDs. [azd-code-reviewer]
} else if deployedName != "" {
agentName = deployedName
}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
There was a problem hiding this comment.
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, anddeepLinkfields. 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
$refagent definitions because the raw adopted YAML hashostand$ref, whilekind/namelive 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 allazure.ai.agentservices and makeapplyAdoptedAgentNameOverridewrite a top-levelnamesibling for$refservices (the include resolver overlays siblings onto the referenced definition); add$refcases to these tests.
if adoptedAgentNameOverrideServiceName(svc) == "" {
continue
}
Add coverage for the publish JSON output contract and handle adopted agent-name overrides for referenced agent services. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.
Suppressed comments (2)
cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:336
- [azd-code-reviewer] The ZIP and digest marker are committed as two independent operations without locking. Concurrent
pack/deploy runs can interleave so one run's ZIP is paired with the other run's digest; future runs then treat the generated ZIP as user-owned and refuse to update or clean it. Serialize the ownership check and both commits with a per-destination interprocess lock, or use a generation protocol that verifies and retries the final pair.
if err := writeTeamsAppPackageAtomically(packagePath, zipBytes); err != nil {
return "", err
}
if err := writeTeamsAppMarkerAtomically(markerPath, []byte(teamsAppPackageMarkerContent(zipBytes))); err != nil {
cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:244
- [azd-code-reviewer] A local commit failure leaves the previous azd-owned package in place, while this function returns an empty path and writes the manual fallback guide. That stale ZIP can still carry the prior manifest/bot binding and be mistaken for the current deployment. Remove the owned package on this error path just as the download-error path does.
written, err := commitTeamsAppPackage(packagePath, markerPath, zipBytes)
if err != nil {
log.Printf("postdeploy: failed to write Teams app package %q: %v", packagePath, err)
return ""
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
cli/azd/extensions/azure.ai.agents/internal/cmd/teams_pack.go:168
- [azd-code-reviewer] This derives the default bot instead of the bot actually used by the completed deployment. Deploy supports custom or identity-bound existing bots and persists their real name/resource group in
envkey.AgentBotNameandenvkey.AgentBotResourceGroup(service_target_agent.go:3040-3051). In those supported cases, pack/publish sends an ARM ID for the wrong or nonexistent bot. Read the persisted bot name and resource group here, using deterministic derivation only as a legacy fallback, and add coverage for a custom bot.
botName := botservice.BotName(agentName, botservice.BotScopeSalt(subscriptionID, resourceGroup))
botArmID := botservice.BotArmID(subscriptionID, resourceGroup, botName)
cli/azd/extensions/azure.ai.agents/internal/cmd/teams_pack.go:140
- [azd-code-reviewer]
readOptionalEnvValuesuppresses everyEnvironment.GetValueerror (listen_activity.go:388-396). These name and version values are required here, so a host/gRPC read failure is converted into the misleading “has not been deployed” result and tells the user to redeploy; the version read below has the same problem. Preserve lookup errors and map only a confirmed empty value toCodeAgentNotDeployed.
serviceKey := toServiceKey(svc.Name)
agentName := readOptionalEnvValue(ctx, azdClient, envName, fmt.Sprintf("AGENT_%s_NAME", serviceKey))
if strings.TrimSpace(agentName) == "" {
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
cli/azd/extensions/azure.ai.agents/internal/cmd/listen.go:417
botNameis the persisted bot selected during deployment and may belong to a resource group other thanAZURE_RESOURCE_GROUP(for example, when deployment reuses a bot found by MSA app ID). Deployment persists the matching bot resource group separately, so using the project resource group here produces an invalid Bot ARM ID. ReadAgentBotResourceGroupalongsideAgentBotName. [azd-code-reviewer]
resourceGroup, rgErr := readEnvValue(ctx, azdClient, envName, "AZURE_RESOURCE_GROUP")
cli/azd/extensions/azure.ai.agents/internal/cmd/publish.go:102
- For an invalid publish scope, this shared resolver's suggestion lists
personalas a valid choice, but the next validation immediately rejectspersonalfor publish. Validate againstteamsPublishScopesor otherwise make the error list onlyshared,tenant, and theorgalias. [azd-code-reviewer]
scope, err := resolveTeamsPackScope(a.flags.scope)
cli/azd/extensions/azure.ai.agents/internal/cmd/init_adopt.go:859
- Legacy agent definitions can place a supported
$refunderconfig, but this check recognizes only an inlineconfig.kind. In that case the override is written to top-levelnamewhile the effective name still comes fromconfig.$ref, so--agent-namesilently has no effect. Treat a config-level$refas legacy and writeconfig.name; add an effective-load regression test for this shape. [azd-code-reviewer]
if legacy := svc.GetConfig(); legacy != nil && legacy.GetFields()["kind"].GetStringValue() != "" {
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:194
- [azd-code-reviewer] The package and digest marker are separate atomic renames with no cross-process serialization. A concurrent
packand postdeploy can interleave as package A, package B, marker B, marker A; both report success but the final package no longer matches its marker, and future runs treat it as user-owned. Use an OS-level per-destination lock around the ownership check, both writes, andremoveOwnedTeamsAppPackage(the repo uses this pattern inpkg/environment/local_file_data_store.go:50-83).
if err := writeTeamsAppPackageAtomically(packagePath, zipBytes); err != nil {
return "", err
}
if err := writeTeamsAppMarkerAtomically(markerPath, []byte(teamsAppPackageMarkerContent(zipBytes))); err != nil {
cli/azd/extensions/azure.ai.agents/internal/cmd/pack.go:105
- [azd-code-reviewer]
buildTeamsAppPackageRequesttrims the display name after this fallback, so--display-name " "becomes an empty manifest name instead of the documented agent-name default. Trim before checking for emptiness.
displayName := a.flags.displayName
if displayName == "" {
displayName = packCtx.agentName
}
cli/azd/extensions/azure.ai.agents/internal/cmd/publish.go:128
- [azd-code-reviewer]
buildTeamsAppPackageRequesttrims the display name after this fallback, so--display-name " "publishes an empty manifest name and reports a whitespace-only name instead of using the documented agent-name default. Trim before checking for emptiness.
displayName := a.flags.displayName
if displayName == "" {
displayName = packCtx.agentName
}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.
Suppressed comments (2)
cli/azd/extensions/azure.ai.agents/internal/cmd/init_adopt.go:859
- [azd-code-reviewer]
config: { $ref: ... }legacy services reach this helper before the reference is resolved, sokindis absent and the code writes a top-levelname. After resolution,ServiceConfigPropsprefers the legacyconfig, making the explicit--agent-nameoverride silently ineffective. Treat a$refinsvc.Configas legacy too.
if legacy := svc.GetConfig(); legacy != nil && legacy.GetFields()["kind"].GetStringValue() != "" {
return "config.name"
}
cli/azd/extensions/azure.ai.agents/internal/cmd/listen_activity.go:171
- [azd-code-reviewer] The ZIP and digest marker are updated as two independent renames without cross-process serialization. Concurrent
pack/postdeploy runs can interleave so the ZIP comes from one run and the marker from another; later runs then classify the generated package as user-owned and refuse to update it. Serialize the ownership check, package write, marker write, rollback, and removal under a per-destination OS file lock.
func commitTeamsAppPackage(packagePath, markerPath string, zipBytes []byte) (string, error) {
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 packDownloads 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 withatk install --scope personal(no Teams admin required).[name] --scope --display-name --app-version --output-dirpersonalpersonal,shared,tenant(orgis accepted as a compatibility alias fortenant).appPackage.zipnext to the agent source (or--output-dir).azd ai agent publishBuilds the same package server-side and publishes it to the Microsoft 365 store, returning the title id, Teams app id, and install deep link.
[name] --scope --display-name --app-version --outputsharedshared— 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.personalis not supported — per-user install is a Teams client action, not a store publish. For that path useazd ai agent pack+atk install --scope personal.--output jsonreturns pure JSON withtitleId,teamsAppId,scope, anddeepLink.Failure transparency
Unlike deploy-time best-effort packaging, these commands report backend errors faithfully. If the platform fails (for example, the Microsoft 365
/publishendpoint errors),azd ai agent publishfails with the underlying service error instead of silently skipping.Testing
go test ./internal/cmd ./internal/pkg/agents/agent_api ./internal/exterrorsgo fix -diff ./...reports no remaining suggestions.azd x pack --rebuild,azd x publish --source local, andazd extension install azure.ai.agents --source local --force.azd ai agent pack echopub073001 --scope personal --output-dir <tmp>:appPackage.zip.manifest.json,default-color-icon.png, anddefault-outline-icon.png.name.short=echopub073001,version=1.0.0,developer.name=Azure AI Foundry, andbotId=119aa5ee-f7db-41ce-a50c-9853b54133a5.azd ai agent publish echopub073001 --scope shared --no-prompt:titleId=T_7921a0e5-c58b-8dbc-d833-ed5ef671eec4.teamsAppId=040abe71-46ba-44ad-a7f1-8d7bdf426062.https://teams.microsoft.com/l/app/040abe71-46ba-44ad-a7f1-8d7bdf426062.azd ai agent publish echopub073001 --no-promptpublishes withscope: shared.azd ai agent publish echopub073001 --no-prompt -o jsonreturns parseable JSON.azd ai agent publish echopub073001 --scope personal --no-promptfails with an actionable message pointing topack+atk install --scope personal.hi, and receivedEcho : hifromechopub073001.Latest E2E validation
Validated with the PR-built local
azure.ai.agentsextension (1.0.0-beta.8, commitc55a3335f) against a fresh Activity echo project:azd ai agent initfrom the Activity quickstart echo sampleazd provision(new Foundry project + ACR)azd deploy(agentechopr933208032120, active version3)azd ai agent publish --scope shared --display-name echopr933208032120hireceivedEcho: hiPublish result used for validation:
{ "titleId": "T_8a6472dd-f4d3-4337-4be1-09da737a5cc2", "teamsAppId": "788aac2c-d433-4f94-84ac-97f68d3ed452" }