Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
5219079
feat(agentconfig): share authoring identity and agent discovery
ReillyBova Sep 11, 2026
c1afef5
fix(agentconfig): sanitize token reads and narrow test warnings
ReillyBova Sep 14, 2026
b559173
fix(agentconfig): preserve actionable credential failures
ReillyBova Sep 18, 2026
2898c62
[1/5] Share authoring identity and deployed-agent discovery (#269)
rebova-microsoft Sep 18, 2026
36beae9
feat(announcements): add agent-scoped authoring contracts
ReillyBova Sep 11, 2026
0c8a658
fix(announcements): preserve repairable read-only draft inputs
rebova-microsoft Sep 18, 2026
2dbb449
Merge pull request #270 from microsoft/users/rebova/org-announcements…
SophieS0ng Sep 24, 2026
0ad13bf
feat(announcements): add account-bound audience discovery
ReillyBova Sep 11, 2026
ecf0de9
fix(announcements): handle Graph audience pagination
SophieS0ng Sep 25, 2026
f122ad7
Merge pull request #271 from microsoft/users/rebova/org-announcements…
SophieS0ng Sep 25, 2026
e183ff9
feat(announcements): expose scoped MCP authoring runtime
ReillyBova Sep 11, 2026
fed6635
fix(announcements): align opener guidance and recovery coverage
rebova-microsoft Sep 18, 2026
78d5c31
Harden announcement runtime error handling
SophieS0ng Sep 25, 2026
2d11583
feat(announcements): wire maker discovery and packaged entry points
ReillyBova Sep 11, 2026
b65f1a3
fix(announcements): keep review drafts open and guidance accurate
rebova-microsoft Sep 18, 2026
8d3b5df
Bump bundled maker profile version
SophieS0ng Sep 25, 2026
ccac21d
Migrate announcement authoring to OData
SophieS0ng Sep 25, 2026
4e9e51c
fix(announcements): harden runtime trust boundaries
SophieS0ng Sep 25, 2026
4c35c32
fix(announcements): validate mutation route keys
SophieS0ng Sep 25, 2026
ee1afed
fix(announcements): harden authoring client lifecycle
SophieS0ng Sep 25, 2026
25a410f
fix(announcements): keep backend codes out of diagnostics
SophieS0ng Sep 25, 2026
5d9da24
Merge latest announcement runtime into maker setup
SophieS0ng Sep 25, 2026
128c50c
fix(announcements): isolate setup imports and clarify limits
SophieS0ng Sep 25, 2026
aa16c19
Merge latest maker setup into OData migration
SophieS0ng Sep 25, 2026
1ea51df
fix(announcements): expose setup completion entry point
SophieS0ng Sep 25, 2026
098a0f4
Merge latest maker setup follow-up into OData migration
SophieS0ng Sep 25, 2026
586cea9
test(announcements): use GUID telemetry fixtures
SophieS0ng Sep 25, 2026
126eca7
test(announcements): align telemetry fixtures with OData identity
SophieS0ng Sep 25, 2026
8b9a546
fix(announcements): harden client lifecycle and telemetry
SophieS0ng Sep 25, 2026
dc6c4bb
fix(announcements): complete maker entry point review
SophieS0ng Sep 25, 2026
1011361
Merge PR 272 lifecycle hardening into PR 273
SophieS0ng Sep 25, 2026
d08a7cd
fix(announcements): enforce the WeveNova OData contract
SophieS0ng Sep 25, 2026
a9b78d2
Merge PR 273 maker wiring into PR 346
SophieS0ng Sep 25, 2026
65f0ddd
fix(announcements): make auth recovery source-aware
SophieS0ng Sep 25, 2026
aba6f40
Merge source-aware auth recovery guidance into PR 346
SophieS0ng Sep 25, 2026
90f03a1
Merge pull request #272 from microsoft/users/rebova/org-announcements…
SophieS0ng Sep 26, 2026
22444c8
feat(announcements): wire maker discovery and packaged entry points
ReillyBova Sep 11, 2026
8e9b18d
fix(announcements): keep review drafts open and guidance accurate
rebova-microsoft Sep 18, 2026
df9e114
Bump bundled maker profile version
SophieS0ng Sep 25, 2026
346ded5
fix(announcements): isolate setup imports and clarify limits
SophieS0ng Sep 25, 2026
9aa91bb
fix(announcements): expose setup completion entry point
SophieS0ng Sep 25, 2026
d0fa90f
fix(announcements): complete maker entry point review
SophieS0ng Sep 25, 2026
0e5eff4
fix(announcements): make auth recovery source-aware
SophieS0ng Sep 25, 2026
b7cf84c
Merge pull request #273 from microsoft/users/rebova/org-announcements…
SophieS0ng Sep 26, 2026
d6906b7
Merge pull request #346 from microsoft/users/sophiesong/org-announcem…
SophieS0ng Sep 26, 2026
12356e0
chore(ci): remove temporary org announcement branch filters
SophieS0ng Sep 28, 2026
dcdf6f3
Merge main into Org Announcements prerelease
SophieS0ng Sep 28, 2026
9bd4e75
test(installer): isolate telemetry identity check
SophieS0ng Sep 28, 2026
874ef1e
Merge branch 'main' into users/rebova/org-announcements-prerelease
apurvabanka Sep 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ For everything that isn't an attempt to use the kit (general questions, code exp
- `/menu`
- `/troubleshoot`
- `/flightcheck`
- `/org-announcements`

2. **Intent hint — natural-language equivalent.** The user isn't typing a slash-command but is unambiguously asking to *run* the kit from this workspace. Examples:
- "How do I set up the kit?" / "How do I run setup?" / "Start the ESS Maker Kit"
- "Run flightcheck" / "Run the readiness check on my agent"
- "Create a topic" / "Connect ServiceNow" / "Scan my agent for errors" — when phrased as a request to *do it now* in this workspace, not as a general "how does this work?" question.
- "Create an organization announcement" / "Post an announcement" / "Manage organization announcements" — when phrased as a request to act in this workspace.

When in doubt, prefer the default behavior (answer normally) over firing the redirect. A user asking "what does /flightcheck do?" is asking a documentation question — answer it from the README and `solutions/ess-maker-skills/` files; do **not** redirect.

Expand All @@ -51,7 +53,7 @@ When (and only when) the trigger conditions above are met, respond with **only**
> 2. Navigate **inside** this folder, then **into** `solutions`, and select `ess-maker-skills`
> 3. Click `Select Folder`
> 4. VS Code will reopen with the kit loaded
> 5. Type your command again (for example `/setup` or `/run`) — it will work this time
> 5. Type your command again (for example `/setup`, `/org-announcements`, or `/run`) — it will work this time
>
> See the [README](README.md) for the full getting-started walkthrough.
>
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ jobs:
run: >-
python -m pytest
tests/mcp/agentconfig_core
tests/mcp/test_import_isolation.py
tests/scripts/test_mcp_config.py
-q

Expand Down Expand Up @@ -137,6 +138,31 @@ jobs:
node extension.test.js
npm run validate

org-announcements:
name: Org Announcements configuration
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'

- name: Install Python dependencies
run: >-
pip install
-r requirements-dev.txt
-r solutions/ess-maker-skills/src/mcp/agentconfig_org_announcements/requirements.txt

- name: Run Org Announcements tests
run: >-
python -m pytest
tests/mcp/agentconfig_org_announcements
tests/setup/test_org_announcements_integration.py
-q

flightcheck-tests:
name: FlightCheck offline test suite
runs-on: ubuntu-latest
Expand Down
14 changes: 12 additions & 2 deletions setup/Install-EssAdk.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -422,11 +422,21 @@ Test 'maker installer identity is instrumented (post-consolidation)' {
# now a compat shim into the unified installer that passes -InstallMode maker,
# the guard would drop all shim events. It must be gone.
$old = $env:ESS_ADK_TELEMETRY
$oldSend = (Get-Command Send-EssTelEvent).ScriptBlock
try {
$env:ESS_ADK_TELEMETRY = ''
$env:ESS_ADK_TELEMETRY = 'on'
Set-Item -Path Function:\Send-EssTelEvent -Value {
param([hashtable]$Envelope)
return 200
}
Initialize-EssInstallTelemetry -Installer 'lite' -InstallMode 'maker'
if (-not $script:EssTel.Ready) { throw 'legacy lite installer identity should be telemetry-ready after consolidation' }
} finally { $env:ESS_ADK_TELEMETRY = $old; $script:EssTel.Ready = $false; $script:EssTel.Completed = $false }
} finally {
Set-Item -Path Function:\Send-EssTelEvent -Value $oldSend
$env:ESS_ADK_TELEMETRY = $old
$script:EssTel.Ready = $false
$script:EssTel.Completed = $false
}
}
Test 'PowerShell emitter no longer guards out the legacy lite installer' {
$emitterSrc = Get-Content $psEmitter -Raw
Expand Down
2 changes: 1 addition & 1 deletion setup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ iex (irm https://raw.githubusercontent.com/microsoft/Employee-Self-Service-Agent

Maker mode is the same install as Developer mode plus the **ESS Maker Profile** extension applying:
- A chat-only layout with all developer surfaces hidden
- Big-button "Quick Actions" rail for common tasks (Connect, Customize landing page, Create, Scan, FlightCheck, Push)
- Big-button "Quick Actions" rail for common tasks (Setup, Customize landing page, Post an announcement, Create, Scan, FlightCheck, Push)
- A built-in tutorial explaining each button

You can switch between Maker mode and Developer mode at any time using the toggle buttons in the Quick Actions panel.
Expand Down
18 changes: 16 additions & 2 deletions solutions/ess-maker-skills/.github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,8 @@ Order of grounding sources (highest to lowest):
microsoft/CopilotStudioSamples Employee Self-Service Agent samples.
3. `src/skills/` - kit-shipped skill instructions for /create, /update,
/delete, /test, /scan, /evaluate, /push, /flightcheck,
/backup-template-configs, /restore-template-configs, and landing-page
configuration.
/backup-template-configs, /restore-template-configs, /org-announcements,
and landing-page configuration.
4. `src/reference/` (other subfolders) - additional kit-shipped guidance.
5. Web fetch / general knowledge - only when none of the above answer the
question and only after telling the user you're falling back.
Expand Down Expand Up @@ -452,6 +452,8 @@ pushed. Run the push pipeline when the maker asks to push local changes.
| Restore or re-apply hybrid Workday HCM template configs | `src/skills/restore-template-configs/SKILL.md` |
| View or configure ESS landing-page branding, quick links, starter prompts, insight cards, name, or icon | `src/skills/landing-page-config/SKILL.md` |
| Invoke any tool from the `ess-landing-page-config` MCP server | `src/skills/landing-page-config/SKILL.md` |
| Create, edit, republish, archive, or manage organization announcements or bulletins | `src/skills/org-announcements/SKILL.md` |
| Invoke any tool from the `ess-org-announcements` MCP server | `src/skills/org-announcements/SKILL.md` |

**Trigger phrases for connect:** "connect ServiceNow", "set up ServiceNow",
"integrate ServiceNow", "connect Workday", "set up Workday", "add ServiceNow",
Expand All @@ -475,6 +477,18 @@ links, starter prompts, Stay Up to Date, Quick Access, the agent name, or the
agent icon, or asks what any landing-page setting controls for employees. Do
not call an AgentConfiguration MCP tool from a generic flow.

**Org Announcements invocation:** Before invoking ANY tool from the
`ess-org-announcements` MCP server, read and follow
`src/skills/org-announcements/SKILL.md`. Its own `list_agent_configs` and
`search_agents` tools resolve missing deployed titleIds; do not start or call
the landing-page server for announcement discovery. This applies whether the user asks to
see, create, edit, republish, archive, or delete an announcement, mentions
announcements, org announcements, bulletins, or alerts, or asks who an
announcement reaches. Org Announcements are scoped to the authenticated tenant
and selected deployed agent's required `titleId`. The tenant is token-derived;
the title is not an audience group or author permission. Do not call an Org
Announcements MCP tool from a generic flow.

**FlightCheck results rendering:** When presenting `/flightcheck` results (Step 3
of `src/skills/flightcheck/SKILL.md`), read `workspace/flightcheck/results.json`
with your file-reading tool and format the summary banner and tables **yourself,
Expand Down
1 change: 1 addition & 0 deletions solutions/ess-maker-skills/.github/prompts/menu.prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Here's what I can help you with:
| Command | What it does |
|---------|-------------|
| `/landing-page` | Configure the branding and content employees see when they open the ESS agent |
| `/org-announcements` | Create and manage announcements for the selected deployed ESS agent |
| `/connect-workday` | Connect the active ESS HR agent to Workday |
| `/connect` | Choose an available integration |
| `/create` | Create a topic, workflow, or evaluation test set locally |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
mode: agent
description: "Create and manage announcements for the selected ESS agent"
---

# Org Announcements

Read `src/skills/org-announcements/SKILL.md` and follow it.
5 changes: 5 additions & 0 deletions solutions/ess-maker-skills/.vscode/mcp.defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
"command": "{pythonExecutable}",
"args": ["server.py"],
"cwd": "${workspaceFolder}/src/mcp/agentconfig_landing_page"
},
"ess-org-announcements": {
"command": "{pythonExecutable}",
"args": ["server.py"],
"cwd": "${workspaceFolder}/src/mcp/agentconfig_org_announcements"
}
}
}
43 changes: 43 additions & 0 deletions solutions/ess-maker-skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,48 @@ Copilot Studio and deployed to the organization. `/setup` installs and extracts
the Power Platform agent; publication, admin approval, and Integrated apps
deployment are separate steps.

### 📢 Post Organization Announcements

Publish announcements for the selected deployed ESS agent and its audiences.
Run `/org-announcements`, ask `Create an announcement`, or use the **Post an
announcement** Quick Action.

- **Standard announcements** carry a title, description, priority, and up to
two actions.
- **Alerts** carry a single link action for time-sensitive notices.
- **Audiences** are security groups, mail-enabled security groups, or classic
distribution groups, searched by name or email in one combined query.
- **Scheduling** publishes an announcement for a start/end window, and expired
announcements can be published again through the normal editor after
reviewing and updating their schedule.
- **Lifecycle** actions archive, unarchive, move back to draft, duplicate, or
delete an announcement.

Describe the announcement in chat and the kit opens a pre-filled editor for
you to review — nothing is saved until you publish or save a draft in that
editor.

Org Announcements are **scoped to the authenticated tenant and selected
agent's `titleId`**, not shared across agents. The current 100 limit and latest
50 archive window apply per tenant-and-agent pair. There is no tenant-wide
fallback. The title is resolved using `list_agent_configs` and `search_agents`
on the `ess-org-announcements` provider. Discovery shares neutral Python code
with the landing-page provider, but does not require its MCP process or
initialize its configuration.

Announcement authoring requires the Org Announcements feature to be enabled
for your tenant, and audience search requires the `Directory.Read.All`
Microsoft Graph permission to be consented in your tenant. Graph uses a
separate resource token for the same authoring tenant and account. The current
account-context check requires readable `tid` and `oid` claims; opaque tokens
or credentials missing those claims return an explicit authentication failure
rather than using a different account. The API still validates tokens and
authorizes every request.

This development surface requires the matching agent-qualified v1.1 backend
and scoped widget. The MCP rejects unscoped canonical responses instead of
silently consuming records from an older backend.

### 📖 Pre-Loaded ESS Documentation, Samples & Best Practices

The kit ships with a complete reference library that the AI agent reads at task time — you don't need to look anything up yourself.
Expand Down Expand Up @@ -375,6 +417,7 @@ Then **run `/setup`** in GitHub Copilot Chat to configure your environment.
|---------|-------------|
| `/setup` | Connect this workspace to an existing editable DA Dev agent |
| `/landing-page` | Configure landing-page branding and content |
| `/org-announcements` | Create and manage announcements for the selected ESS agent |
| `/connect` | Explain the DA-GA product extension requirement |
| `/create` | Create a topic, workflow, or evaluation test set locally |
| `/update` | Update a topic, workflow, or evaluation test set locally |
Expand Down
8 changes: 8 additions & 0 deletions solutions/ess-maker-skills/src/mcp/agentconfig_core/_odata.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ def _escape_odata_literal(value: str, name: str) -> str:
return _validate_odata_string(value, name).replace("'", "''")


def _validate_title_id(title_id: str) -> str:
"""Validate the opaque EmployeeAgents key shared by authoring surfaces."""
_validate_odata_string(title_id, "titleId")
if len(title_id) > 256:
raise ValueError("titleId must not exceed 256 characters")
return title_id


def _require_odata_id(value: str, name: str) -> str:
"""Validate a non-empty, control-char-free id and encode it as an OData key."""
return urllib.parse.quote(_escape_odata_literal(value, name), safe="")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

"""Read-only deployed-agent discovery shared by feature-owned MCP providers."""

from __future__ import annotations

from typing import Any

from base_client import AgentConfigApiError, AgentConfigBaseClient


_MAX_SEARCH_LENGTH = 256


def _convert_key_case(value: Any, *, upper: bool) -> Any:
if isinstance(value, list):
return [_convert_key_case(item, upper=upper) for item in value]
if not isinstance(value, dict):
return value
converted: dict[str, Any] = {}
for key, item in value.items():
if key and key[0].isalpha():
first = key[0].upper() if upper else key[0].lower()
converted_key = first + key[1:]
else:
converted_key = key
converted[converted_key] = _convert_key_case(item, upper=upper)
return converted


def _to_api_payload(value: Any) -> Any:
return _convert_key_case(value, upper=True)


def _to_tool_payload(value: Any) -> Any:
return _convert_key_case(value, upper=False)


def _unwrap_agent_collection(payload: Any) -> list[dict[str, Any]]:
if isinstance(payload, list):
return payload
if isinstance(payload, dict) and isinstance(payload.get("value"), list):
return payload["value"]
raise AgentConfigApiError(
"AgentConfiguration API returned an invalid collection response"
)


class AgentDiscoveryClient(AgentConfigBaseClient):
"""Discover deployed titleIds without initializing any feature configuration.

Feature clients retain their own base URL, transport, and authentication.
Explicit response conversion keeps discovery independent of each feature's
canonical payload casing and collection routes.
"""

def _agent_collection_path(self) -> str:
return f"tenants('{self.tenant_id}')/EmployeeAgents"

async def list_agent_configs(self) -> list[dict[str, Any]]:
payload = await self._request(
"GET", self._agent_collection_path(), transform_payload=False
)
return _unwrap_agent_collection(_to_tool_payload(payload))

async def search_agents(self, search_string: str) -> list[dict[str, Any]]:
if not isinstance(search_string, str) or not search_string.strip():
raise ValueError("searchString must be a non-empty string")
normalized = search_string.strip()
if len(normalized) > _MAX_SEARCH_LENGTH:
raise ValueError(
f"searchString must not exceed {_MAX_SEARCH_LENGTH} characters"
)
payload = await self._request(
"POST",
f"{self._agent_collection_path()}/SearchAgents",
json={"SearchString": normalized},
transform_payload=False,
)
return _unwrap_agent_collection(_to_tool_payload(payload))
Loading
Loading