Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 8 additions & 8 deletions .github/copilot-instructions.md

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ test-local (no prerequisite)

**Phase 9.7.2d** validates environment configuration before either path proceeds. For prod: confirms `a365.generated.config.json` has `completed: true` and non-empty `resourceConsents` (else GA consent handoff is pending); confirms `.env`/`appsettings.json` has agentic-auth + LLM + observability vars; reminds the user that cloud env vars must be set at the cloud platform (`az webapp config appsettings set` / `eb setenv` / `gcloud run services update --set-env-vars`), not just locally; confirms HTTPS messaging endpoint. For local: confirms AgentsPlayground is installed and `.m365agentsplayground.yml` is configured when using agentic auth. Authoritative Microsoft Learn refs: [test-with-devtunnels](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/test-with-devtunnels), [testing](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/testing), [deploy-agent-azure](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/deploy-agent-azure), [deploy-agent-aws](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/deploy-agent-aws), [deploy-agent-gcp](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/deploy-agent-gcp). **Automatically** runs `instrument-observability` (only when `has_obs = false`) and **optionally** offers `add-workiq-tools` (only when `has_workiq = false`). The skill does NOT hand-edit `manifest.json`. Reference: [Create agent instance — Microsoft Learn](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/create-instance).
`a365-setup` outputs a mandatory intro message, detects stack/language/CEA/`hasBlueprintConfig`, and the three skill-state flags **`has_aiteammate_structure`**, **`has_obs`**, **`has_workiq`** (the same primary flags that drive `make-ai-teammate` Phase 0C's 8-row matrix). Always updates the a365 CLI to latest (explicit exception to the ✅-skip rule), checks for an existing Azure CLI session before logging in, shows a ✅/❌ prerequisite summary and only processes ❌ missing tools. Asks the blueprint question (reuse vs fresh) then asks **capabilities first** — capability options are auto-filtered: Observability is hidden if `has_obs = true`, WorkIQ is hidden if `has_workiq = true`, the menu collapses to Register + WorkIQ when `(has_aiteammate_structure && has_obs)` (legacy "already an AI Teammate" route, computed inline — the legacy `hasAITeammateChanges` field is **derived, no longer stored**). If AI Teammate is selected, auth mode is skipped (always `agentic-user`); if non-AI Teammate, asks `obo` or `s2s`. Cache fields written: `agentStack`, `programmingLanguage`, `usesTeamsOrCopilot`, `hasBlueprintConfig`, `has_aiteammate_structure`, `has_obs`, `has_workiq`, `agentType`, `authMode`, `reuseBlueprint`, `existingBlueprintId`. Delegates: AI Teammate path → `make-ai-teammate`; all other paths → `make-a365-agent`.
`make-a365-agent` checks for an existing blueprint config before collecting inputs — if found, asks the developer whether to reuse (skips `a365 setup all`) or create fresh. Runs `a365 setup all --authmode obo|s2s` for non-AI Teammate paths; add `--m365` for CEA agents and follow with `a365 setup permissions bot`. `Agent365.Observability.OtelWrite` is auto-granted at provisioning, but other permission grants (Graph, Bot API, custom resources) require Global Administrator consent — when the developer isn't a GA, `a365 setup all` automatically prints next-steps (typically a PowerShell script) for a GA to complete. There is no separate `setup admin` subcommand. Then conditionally invokes `instrument-observability` and `add-workiq-tools`.
`make-a365-agent` checks for an existing blueprint config before collecting inputs — if found, asks the developer whether to reuse (skips `a365 setup all`) or create fresh. Runs `a365 setup all --authmode obo|s2s` for non-AI Teammate paths; add `--m365` for CEA agents and follow with `a365 setup permissions bot` (this still configures the Observability API for the CEA flow; harmless because telemetry uses S2S). No Observability API permission is needed: newer CLI versions skip `Agent365.Observability.OtelWrite` for blueprint agents and exit 1 when registration fails or cannot be verified; older versions may still grant OtelWrite (harmless) and may exit 0 after a failed registration, so check setup output and rerun `a365 setup all --agent-registration-only` if needed. Other permission grants (Graph, Bot API, custom resources) require Global Administrator consent — when the developer isn't a GA, `a365 setup all` automatically prints next-steps (typically a PowerShell script) for a GA to complete. There is no separate `setup admin` subcommand. Then conditionally invokes `instrument-observability` and `add-workiq-tools`.
`add-workiq-tools` and `instrument-observability` read `.a365-workspace-detection.local.json` to skip re-detection and verify prerequisites. `add-workiq-tools` Phase 0B includes a **framework support guard** that hard-stops on unsupported `(programmingLanguage, agentStack)` pairs (Python LangChain / Claude / CrewAI; Node.js Semantic Kernel / Google ADK) before any CLI command runs. Phase 4 branches on the cached `agentStack` into 11 framework-specific sub-sections (§4.1 .NET Agent Framework through §4.11 Python Azure AI Foundry); the stop-hook validator (`validate-add-workiq-tools.js`) is also framework-aware and requires the framework-matching symbol (e.g., `AddToolServersToAgentAsync` for .NET SK, `add_tool_servers_to_agent` for Python). **Phase 4.5 (gated)** offers the Word `@mention` notification handler when *both* gates pass: `programmingLanguage = NodeJS && agentStack = LangChain`, AND `mcp_WordServer` is in `ToolingManifest.json`. Wires `proactive: {}`, per-user conversation index, and a `NotificationType.WpxComment` branch — best-effort because no Microsoft Node.js sample is published yet. Best-effort branches (Python SK, Python/.NET Azure AI Foundry, and the Phase 4.5 @mention handler) mark all generated lines with `// A365 WorkIQ — best-effort wiring (verify against SDK source before production)`.

`purview-dlp-integration` is **additive** and independent of observability / WorkIQ. It auto-discovers the app (client) id, display name, blueprint id, and current Graph scopes from `a365.config.json` + `a365.generated.config.json`, then asks only for what's missing (DLP policy choice, sensitive info type, admin UPN, agentic auth handler name). It detects the language and authentication and copies ONE generic env-driven guard (`assets/purview.ts` / `purview.py` / `purview.cs` for delegated agents; `assets/purview-s2s.ts` for Node.js client-secret FMI S2S; .NET is best-effort). Minimal wiring adds an **INPUT gate** before the LLM and optional **output auditing** before the reply; the supplied policy does not filter sensitive responses. Delegated guards use the agent's own token at `/me` with `Content.Process.User` appended by `Grant-DelegatedGraphScope.ps1`. Node.js S2S uses the agent identity FMI token at `/users/{sponsor}/...` with `Content.Process.All` from `Grant-ContentProcessAppRole.ps1`, never a blueprint app-only token or `admin-consent` on the blueprint. Do not switch managed-identity-only agents to client-secret authentication. Manual IDs replace config discovery, not missing authentication; a plain bot without a supported path must stop and route to `a365-setup` before edits. The guards always set `contentEntry.name` and fail closed by default. Policy choice remains new (`New-AiAppDlpPolicy.ps1`), existing (`-ListExisting`), or skip. Verify the `[purview] uploadText -> BLOCKED (… errors=0)` log and that the LLM was not called, not `DistributionStatus`. The stop-hook validator is report-first because disabled/policy-pending bring-up is valid.
Expand Down Expand Up @@ -250,9 +250,11 @@ Skills reference shared docs via `Read ${CLAUDE_PLUGIN_ROOT}/shared/<file>.md`.
- AI Teammate → `obo` (signed-in user OBO) or `agentic-user` (agent's own Azure AD user — persistent M365 identity)
- Agent (Non AI Teammate) → `obo` (On-Behalf-Of) or `s2s` (Service Principal, no user token)

All three `authMode` values use an auth handler reference in SDK code — the difference is Azure AD provisioning. For OBO paths: .NET reads `authHandlerName` from config (`AgentApplication:AgenticAuthHandlerName`); Node.js passes `agentApplication.authorization` (the auth object) to `RefreshObservabilityToken`; Python uses `auth_handler_id=self.auth_handler_name` (from config) in `exchange_token()` — never hardcode `"AGENTIC"`. Agent IDs are always resolved dynamically from TurnContext — .NET: `turnContext.Activity.GetAgenticInstanceId()` (service principal object ID); Node.js/Python: `recipient.agenticAppId` / `agentic_app_id`. Results are cached in `.a365-workspace-detection.local.json` under `agentType` and `authMode` fields so subsequent skill invocations skip re-questioning. If `authMode = s2s` and the skill is `add-workiq-tools`, the skill exits immediately — WorkIQ is not available for s2s agents (requires a delegated OBO token).
Telemetry never goes through the auth handler: every `authMode` exports over the S2S route with an app-only token for the exporting agent identity (the S2S route rejects delegated `scp` tokens). The auth handler reference is for workload calls (MCP / Graph): .NET reads `authHandlerName` from config (`AgentApplication:AgenticAuthHandlerName`); Node.js uses `agentApplication.authorization` (the auth object); Python uses `auth_handler_id=self.auth_handler_name` (from config) — never hardcode `"AGENTIC"`. Agent IDs are resolved dynamically from TurnContext — .NET: `turnContext.Activity.GetAgenticInstanceId()` returns `Recipient.AgenticAppId` (the agent identity's app/client ID); Node.js/Python: `recipient.agenticAppId` / `agentic_app_id`. Only non-agentic turns of non-AI-Teammate agents fall back to the provisioned agent identity in config (never the blueprint). Results are cached in `.a365-workspace-detection.local.json` under `agentType` and `authMode` fields so subsequent skill invocations skip re-questioning. If `authMode = s2s` and the skill is `add-workiq-tools`, the skill exits immediately — WorkIQ is not available for s2s agents (requires a delegated OBO token).

**S2S scaffold requirement:** When `authMode = s2s`, `instrument-observability` creates a scaffold token-service file per language: .NET creates `Observability/ObservabilityServiceExtensions.cs` + `Observability/ObservabilityTokenService.cs`, Node.js creates `observability/observability-token-service.ts`, Python creates `observability/observability_token_service.py`. Each acquires and refreshes the Observability API token via MSAL client credentials targeting `api://9b975845-388f-4429-889e-eab1ef63949c/.default`. The .NET files additionally provide the `AddAgent365Observability()` / `Agent365ObservabilityContext` DI extensions that replace `AddAgenticTracingExporter()` and per-turn `RegisterObservability()`. The validator (`validate-instrument-observability.js`) accepts either the OBO signal (`BaggageBuilder` / `BaggageTurnMiddleware`) or the S2S signal to pass the context check.
**App-only telemetry token (every auth mode):** For `obo` / `agentic-user`, `instrument-observability` creates an app-only token resolver that reuses the hosting connection's blueprint credential (FMI assertion for the turn's agent identity → agent-identity `client_credentials` for the OBS scope): `Observability/AgentAppTokenResolver.cs` (.NET), `observability/app-token-resolver.ts` (Node.js), or `observability/app_token_resolver.py` (Python — sync `resolve` plus a per-turn async `prefetch`). No per-turn delegated `RegisterObservability(..., AgenticTokenStruct)` / `refreshObservabilityToken(...)` / `exchange_token(...)` is generated, and existing delegated wiring is migrated. Every mode sets the S2S route flag (`o.Agent365.UseS2SEndpoint = true` — `o.Agent365.Exporter.*` on `Microsoft.OpenTelemetry` 1.0.2 and earlier; `useS2SEndpoint: true`; `a365_use_s2s_endpoint=True`). Registered blueprint agent instances need no `Agent365.Observability.OtelWrite` permission or admin consent (subject to service policy); AI Teammates complete the `OtelWrite` application-role step `a365 setup all --aiteammate` prints.

**S2S scaffold requirement:** When `authMode = s2s`, `instrument-observability` creates a scaffold token-service file per language: .NET creates `Observability/ObservabilityServiceExtensions.cs` + `Observability/ObservabilityTokenService.cs`, Node.js creates `observability/observability-token-service.ts`, Python creates `observability/observability_token_service.py`. Each acquires and refreshes the Observability API token via MSAL client credentials targeting `api://9b975845-388f-4429-889e-eab1ef63949c/.default`. The .NET files additionally provide the `AddAgent365Observability()` / `Agent365ObservabilityContext` DI extensions that replace `AddAgenticTracingExporter()` and per-turn `RegisterObservability()`. The validator (`validate-instrument-observability.js`) accepts either the OBO signal (`BaggageBuilder` / `BaggageTurnMiddleware`) or the S2S signal to pass the context check. When the unified distro is used, it also requires the S2S route flag and a token resolver, both passed in the distro call itself (`o.Agent365.TokenResolver` / `tokenResolver` / `a365_token_resolver`), in every auth mode, and it fails the session on delegated telemetry wiring (`refreshObservabilityToken(...)`, `RegisterObservability(..., AgenticTokenStruct)`, `exchange_token(...)` for the observability scope).

---

Expand Down
23 changes: 21 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ agent365-skills/
them with URL + action and continue.

11. **`a365-code-validator` is report-first.** It diagnoses exporter activation, agent-id
binding, semantic span coverage, S2S/OBO endpoint mismatches, and live Blueprint grants /
effective inheritance through read-only `a365 query-entra` checks when login is already
binding, semantic span coverage, delegated (OBO-route) telemetry or a missing S2S route flag,
and live Blueprint grants / effective inheritance through read-only `a365 query-entra` checks when login is already
available. It then asks whether to apply safe fixes, create a fix plan, or stop. It must
not provision, install packages, mutate Graph, grant permissions, or run `a365 publish`.

Expand All @@ -125,6 +125,25 @@ agent365-skills/
(`validate-purview-dlp-integration.js`) is report-first (advisory `findings`, always
`ok: true`) because "start disabled / skip policy" is a valid bring-up state.

13. **Telemetry always uses the S2S route with an app-only token, in every `authMode`.** The
S2S route rejects delegated (`scp`) tokens, so OBO / Agentic User tokens are only for workload
calls (MCP / Graph). `obo` / `agentic-user` agents get an app-only resolver that reuses the
hosting connection's blueprint credential (`AgentAppTokenResolver.cs` /
`app-token-resolver.ts` / `app_token_resolver.py`); `s2s` agents get the FMI token-service
scaffold. Always set the S2S route flag (`o.Agent365.UseS2SEndpoint = true`,
`useS2SEndpoint: true`, `a365_use_s2s_endpoint=True`). Never generate a per-turn delegated
telemetry token (`RegisterObservability(..., AgenticTokenStruct)`,
`refreshObservabilityToken(...)`, `exchange_token(...)` for the observability
scope), and never add the delegated `OtelWrite` scope for telemetry. Registered blueprint agent
instances need no `Agent365.Observability.OtelWrite` permission or admin consent, so never make
an OBS grant a required step for them. Newer `a365 setup all` versions skip OtelWrite for
blueprint agents and fail when registration fails or cannot be verified; older versions may
still grant OtelWrite (harmless) and may exit 0 after a failed registration, so check setup
output and rerun `a365 setup all --agent-registration-only` if needed. For blueprint agents,
a 403 `insufficient_scope` means the instance isn't registered. AI Teammates complete
the `OtelWrite` application-role step that `a365 setup all --aiteammate` prints. The application
role is always an accepted fallback on the S2S route.

---

## Testing
Expand Down
Loading
Loading