Add pack+sideload Teams app script to activity-protocol postdeploy (#9172)#9188
Open
v1212 wants to merge 22 commits into
Open
Add pack+sideload Teams app script to activity-protocol postdeploy (#9172)#9188v1212 wants to merge 22 commits into
v1212 wants to merge 22 commits into
Conversation
…zure#9172) For activity-protocol agents, `azd deploy` already writes TEAMS_APP_SETUP.md. This additive change also emits a runnable pack-and-sideload script (pack-and-sideload-teams-app.ps1 and .sh) next to the agent source. The script builds the Teams app package with the bot id baked in from the deploy and installs it for the current user via `atk install --scope Personal` (no Teams admin required) in one command. It checks for and installs the atk CLI, launches `atk auth login` when needed, embeds the required icons, is idempotent (a stable per-agent Teams app id means re-runs update the same app instead of duplicating), and honors SKIP_TEAMS_INSTALL=1. The guide now points to the script as the fast path and lists its prerequisites. Purely additive: no change to existing bot provisioning or guide behavior. 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. |
Split the two base64 icon constants into <=150-char chunks joined with '+' to satisfy the lll (line-length 220) linter; reassembled value is identical. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
v1212
marked this pull request as ready for review
July 17, 2026 08:56
v1212
requested review from
JeffreyCA,
glharper,
huimiu,
hund030,
therealjohn,
trangevi and
trrwilson
as code owners
July 17, 2026 08:56
|
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. |
Wrap the icon base64 const block with cspell:disable/enable so the encoded PNG data does not trip the spell checker. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Contributor
There was a problem hiding this comment.
Pull request overview
Adds generated Teams packaging and sideloading scripts for activity-protocol agents.
Changes:
- Generates Bash and PowerShell scripts with deterministic app IDs and embedded icons.
- Integrates scripts into postdeploy guidance.
- Adds tests, documentation, and a changelog entry.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
teams_sideload_script.go |
Generates and selects sideload scripts. |
teams_sideload_script_test.go |
Tests script generation and IDs. |
listen_activity.go |
Adds scripts to postdeploy output. |
teams_pack_sideload.sh |
Bash packaging and installation flow. |
teams_pack_sideload.ps1 |
PowerShell packaging and installation flow. |
teams_app_setup_guide.md |
Documents the generated scripts. |
CHANGELOG.md |
Records the enhancement. |
- teams_pack_sideload.{sh,ps1}: move the SKIP_TEAMS_INSTALL guard to after the
package is built so it is a true build-only mode (was skipping packaging).
- Bound Teams manifest name.short (<=30) and description.short (<=80) per v1.19,
since agent names may be up to 63 chars.
- sh: make the TitleId grep tolerate no match (|| true) so the manual-sideload
fallback stays reachable under 'set -euo pipefail'.
- listen_activity.go: emit a shell-safe run hint via sideloadRunCommand (quotes
the path, pwsh call operator for .ps1) per cli/azd/AGENTS.md.
- Remove the CHANGELOG entry; per azure.ai.agents/AGENTS.md the CHANGELOG is
only updated in the dedicated version-bump release PR.
- Add tests for build-only ordering, manifest-field truncation, fallback
reachability, and shell-safe run command.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Assemble expected values from the input path instead of a single long string literal so gosec no longer flags the test as a potential hardcoded credential. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Round-9 review: len(scriptPaths) > 0 treated a partial write as success. If the current-OS script name collided with a user-owned file but the other script was written, preferredSideloadScript returned empty, the collision note was suppressed, and the guide still advertised both filenames -- including the unrelated user file. Gate scriptsGenerated on len(scriptPaths) == teamsSideloadTargets (a new const = the number of shells scripted), so the guide/output only advertise the fast path when every promised script was actually generated; a partial write now shows the collision note and the manual steps. printTeamsNextSteps only prints the fast path when scriptsGenerated is true. Tests assert the happy path equals teamsSideloadTargets and a name collision yields a sub-target partial write. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Round-10 review: - printTeamsNextSteps: when the fast path was suppressed (partial write) and the guide also failed to write, the manual fallback was skipped because it keyed off scriptPath == "" rather than whether a next action had been shown. Track fastPathShown and print the inline manual steps whenever the guide is missing and the fast path was not advertised, so the user is never left without a next action pointing at an unwritten guide. - teams_app_setup_guide.md: the PowerShell build-only example set $env:SKIP_TEAMS_INSTALL for the whole session, so a later normal run in the same terminal silently stayed in build-only mode. Wrap it in try/finally that clears the variable after the one run (including on failure); the bash form is already per-command. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
…ripts Address Copilot review round 11 on PR Azure#9188: - Version idempotency: the previous time-derived version only advanced every 2 seconds, so two valid reruns in the same 2-second bucket produced the SAME version and Teams rejected the second update. Persist a monotonic build number next to the script (.teams-app-version), seeded from wall-clock seconds but always at least last + 1, so every rerun is strictly higher -- even within the same second or after a backwards clock adjustment. It is encoded into two bounded components (minor = N/65536, patch = N%65536) so each stays <= 65535 (minor < 65535 until ~2106). Applied to both the pwsh and bash scripts. - Chmod failure: writeTeamsSideloadScripts still appended a script to the generated list when os.Chmod failed, so the guide could advertise a script the user cannot execute. On chmod failure, skip the target so it counts as not-generated and the manual fallback is shown instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Address Copilot review round 12 on PR Azure#9188: - Symlink safety: the .teams-app-version counter lived in the user-controlled service directory, and a pre-existing symlink there would be followed and its target truncated when the generated script ran -- letting a planted link overwrite an arbitrary user-writable file. Both scripts now refuse to read from or write through a symlink / reparse point / directory: the read is gated on a plain regular file, and the write goes to a temp file that is atomically moved into place (replacing the link itself, never its target). On such a collision the counter is simply not persisted and the time-derived version is used. - atk login command: several user-facing messages and the guide prerequisite referred to the bare parent command 'atk auth login', which does not start a sign-in on the current CLI. They now use the concrete 'atk auth login m365' invocation the scripts already run. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Address Copilot review round 13 on PR Azure#9188: The current ATK CLI prints "Cannot get token. Use 'atk account login m365' to log in the correct account." when no M365 account is signed in. The login-detection regex in both generated scripts did not match that wording, so a fresh user skipped the promised automatic login and fell straight through to the install-failed guidance. - Extend the bash (ERE) and pwsh (.NET) login-required patterns with 'cannot get token' and 'log in the correct account'. - Add TestTeamsSideloadLoginDetection: asserts an equivalent pattern matches the real ATK unauthenticated error and not a successful install line, and that both embedded scripts carry the covering phrases. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Address Copilot review round 14 on PR Azure#9188: The pack+sideload scripts, setup guide, and version-state file used fixed names. If two activity services resolve to the same project: source directory, each postdeploy recognized the same generic marker and overwrote the other's scripts/guide with its own bot id -- both services then advertised one path but only the last agent's package installed (and it raced under parallel, graph-driven deploy). - Make the generated marker agent-specific ("... for bot <msaAppId>") and add canWriteGeneratedFile, which only (over)writes an absent path or a regular file this same agent generated. A user-owned file, a non-regular file, or a file another agent generated in a shared source dir is left untouched, so the second service reports not-generated and shows the manual fallback instead of clobbering the first. - Apply the same guard to the setup guide (previously overwritten unconditionally). - Scope the version-state file per Teams app id (.teams-app-version-<appId>) so two apps sharing a directory never cross-contaminate their version counters. - Tests: refresh case now seeds this agent's marker; add TestWriteTeamsSideloadScriptsRejectsOtherAgent covering the shared-dir case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Comment on lines
+51
to
+52
| func teamsGeneratedMarkerFor(msaAppID string) string { | ||
| return teamsSideloadGeneratedMarker + " for bot " + msaAppID |
Comment on lines
+110
to
+111
| func deterministicTeamsAppID(msaAppID string) string { | ||
| return uuid.NewSHA1(teamsAppIDNamespace, []byte("foundry-teams-app:"+msaAppID)).String() |
| // Do not clobber a user-owned file that shares this name, or a script a | ||
| // different activity service generated for another agent -- only refresh | ||
| // a regular file we generated for this same agent. | ||
| if ok, reason := canWriteGeneratedFile(scriptPath, msaAppID); !ok { |
Comment on lines
+167
to
+169
| if ok, reason := canWriteGeneratedFile(guidePath, msaAppID); !ok { | ||
| log.Printf("postdeploy: Teams setup guide %q %s", guidePath, reason) | ||
| return "" |
Comment on lines
+304
to
+307
| // TestTeamsSideloadScriptBuildOnly asserts the SKIP_TEAMS_INSTALL opt-out is a | ||
| // build-only mode: the package (zip) is produced first and only the atk install | ||
| // is skipped. It verifies this via source ordering rather than executing the | ||
| // scripts (which would need a real atk/npm/pwsh|bash on both CI OSes). |
The word "unstat-able" in a code comment tripped the cspell-lint CI check. Reword to plain English; no behavior change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7b480c5f-6c14-4d28-a19a-634f28621671
Comment on lines
+51
to
+52
| func teamsGeneratedMarkerFor(msaAppID string) string { | ||
| return teamsSideloadGeneratedMarker + " for bot " + msaAppID |
Comment on lines
+200
to
+203
| if ok, reason := canWriteGeneratedFile(scriptPath, msaAppID); !ok { | ||
| log.Printf("postdeploy: Teams sideload script %q %s", scriptPath, reason) | ||
| continue | ||
| } |
Comment on lines
+167
to
+169
| if ok, reason := canWriteGeneratedFile(guidePath, msaAppID); !ok { | ||
| log.Printf("postdeploy: Teams setup guide %q %s", guidePath, reason) | ||
| return "" |
| @@ -1,3 +1,4 @@ | |||
| <!-- Generated by 'azd deploy' (activity protocol) for bot {{.MsaAppID}}. Safe to edit or delete. --> | |||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Purely additive enhancement (issue #9172). For activity-protocol agents,
azd deployalready writesTEAMS_APP_SETUP.mdnext to the agent source. This PR also emits a runnable pack-and-sideload script (pack-and-sideload-teams-app.ps1and.sh) so the user can go from a fresh deploy to a Teams-installed app with one command, instead of following the manual guide steps.What the script does
atk install --scope Personal— no Teams admin / org catalog needed.atkCLI (npm) if missing; launchesatk auth loginwhen not signed in.SKIP_TEAMS_INSTALL=1to only build the package.The guide now points to the script as the fast path and lists its prerequisites.
Additive / no behavior change
listen_activity.goonly to call the new writer and pass the script path into the existingprintTeamsNextStepspointer.Testing
go build ./...,go vet ./...,go test ./internal/cmd/all green.atkshim: manifest botId == msaAppID, deterministic app id, valid icons, zip entries at package root.atk auth login m365verified against atk 1.1.11.Closes #9172