Add scrum-master scenario: Scrum Master autopilot using Node.js SDK - #334
Add scrum-master scenario: Scrum Master autopilot using Node.js SDK#334keshav Keshari (keshavk-msft) wants to merge 9 commits into
Conversation
Scenario extension on top of the OpenAI + Agent 365 base sample: an autonomous Scrum Master that runs standups, reconciles the Jira board, chases blockers via MCP Calendar, warns on sprint risk, answers grounded Q&A, and posts a management-ready sprint close report. Runs out of the box with JIRA_MODE=mock (no Atlassian account required). Live mode uses Jira Cloud REST + delegated Microsoft Graph (Sites.ReadWrite.All + Sites.Manage.All) via device-code flow. * nodejs / Microsoft Agent 365 SDK + OpenAI Agents SDK + MCP tools * Adaptive Card DMs, per-user rolling context, deterministic classifier * Sibling azure-functions/ package hosts the nightly/weekly timers
Enables reviewers to try live mode (JIRA_MODE=live) without hand-crafting issues: * src/scripts/seed-jira-sample.ts — creates 2 stories + 5 sub-tasks and a future sprint on a pre-existing Jira Scrum project. Idempotent by summary. Reuses accountIds from team.sample.json so a single edit propagates through the whole seed. * src/scripts/sprint.sample.json — topology (edit to customise). * docs/sharepoint-schema.md — human-readable reference for every SMA_* list column, matching LIST_SCHEMAS in services/sharepoint.ts. * package.json — new npm script: seed:jira
Consolidates a single top-level H1 (was two) and reorders content for a first-time reader: intro paragraph -> features (7 MVPs) -> architecture diagram -> prerequisites -> quick start in mock mode -> full setup in live mode -> capability try-it table -> reference sections. Adds: * Mermaid architecture diagram * Quick start (mock mode, no external deps) as the fast path * Full configuration reference table with defaults and required-for * Try-each-capability table with trigger + expected + handler link * Link to docs/sharepoint-schema.md Drops: * Duplicated base-sample content (identity, install, typing, multi-message) now referenced via a link at the top * Redundant POC-flavoured framing
Additive-only improvements ported from patterns in the Chief-of-Staff sample (PR microsoft#333). Zero behaviour change to existing handlers. * src/util/logger.ts (new) — level-based logger with LOG_LEVEL, timestamped scope tags, and automatic secret redaction. Available for future use; existing console.log calls left in place. * src/util/httpLogger.ts (new) — global axios interceptor for outbound Jira / Graph / MCP tracing. Off by default, gated behind LOG_HTTP=true. * src/startup-check.ts (new) — one-shot boot banner printing every relevant env var with [MISSING] markers so misconfig surfaces before the first handler runs. * src/index.ts — wire installHttpLogging + printStartupBanner immediately after configDotenv(); add unhandledRejection and uncaughtException handlers so a stray connector 502 no longer tears down the scheduler. * docs/design.md — replace stub with full design doc: 12 numbered sections including per-flow mermaid sequence diagrams, determinism boundary, concurrency guarantees, and extension points. * README.md — trim architecture / reconcile rules / warn thresholds / calendar path (moved to design.md); add TOC, First live proof smoke test, Troubleshooting matrix, and Deploy to Azure sections. * .env.template — document LOG_LEVEL and LOG_HTTP toggles.
There was a problem hiding this comment.
Pull request overview
Adds a new Scrum Master autonomous scenario under scenarios/scrum-master/, built on the Microsoft Agent 365 SDK + OpenAI Agents SDK, with integrations for Jira (live + mock), SharePoint Lists state, proactive Teams messaging via Adaptive Cards, and optional Azure Functions timers to drive scheduled ceremonies.
Changes:
- Introduces the Scrum Master scenario agent (message routing, slash commands, standup/reconcile/chase/warn/sprint-summary flows) plus supporting services (Jira, SharePoint, Graph delegated auth, MCP Calendar).
- Adds provisioning + seed scripts for SharePoint lists, helper roster, and optional Jira sample data.
- Adds scenario packaging/config (ToolingManifest, env template, manifests, Azure Functions sibling project, build tooling).
Reviewed changes
Copilot reviewed 59 out of 63 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| scenarios/scrum-master/tsconfig.json | TypeScript compiler configuration for the scenario package. |
| scenarios/scrum-master/ToolingManifest.json | Declares MCP servers (Mail/Calendar) used by the scenario. |
| scenarios/scrum-master/src/util/logger.ts | Scenario-level log helper with redaction/truncation. |
| scenarios/scrum-master/src/util/httpLogger.ts | Optional global axios request/response tracing. |
| scenarios/scrum-master/src/token-cache.ts | In-memory token cache + token resolver for observability export. |
| scenarios/scrum-master/src/startup-check.ts | Startup banner summarizing key env configuration. |
| scenarios/scrum-master/src/services/team-roster.ts | SharePoint-backed team roster with cached ConversationReferences. |
| scenarios/scrum-master/src/services/sharepoint.ts | Graph CRUD wrapper for SharePoint lists + report upload helper. |
| scenarios/scrum-master/src/services/session-store.ts | In-memory standup session tracking for fan-in/cutoffs. |
| scenarios/scrum-master/src/services/proactive.ts | Proactive messaging helper (continueConversation wrapper). |
| scenarios/scrum-master/src/services/jira.ts | Jira client abstraction (live REST + mock implementation). |
| scenarios/scrum-master/src/services/jira-tool.ts | OpenAI Agents function-tools for grounded Jira Q&A. |
| scenarios/scrum-master/src/services/issue-labels.ts | Helpers to map Jira keys to “Task-N” labels and clean titles. |
| scenarios/scrum-master/src/services/helperMatcher.ts | Keyword-based helper matching for blocker chase flow. |
| scenarios/scrum-master/src/services/graph.ts | Delegated Graph auth via MSAL device-code + file token cache. |
| scenarios/scrum-master/src/services/calendar.ts | Calendar ops via MCP CalendarTools using a scenario-specific agent. |
| scenarios/scrum-master/src/scripts/team.sample.json | Sample team roster seed data (placeholder identities). |
| scenarios/scrum-master/src/scripts/sprint.sample.json | Sample sprint topology for optional Jira seeding. |
| scenarios/scrum-master/src/scripts/setup-sharepoint.ts | Idempotent SharePoint list + library provisioning script. |
| scenarios/scrum-master/src/scripts/seed-team.ts | Seeds TeamMembers list (optionally patches current user AAD id). |
| scenarios/scrum-master/src/scripts/seed-jira-sample.ts | Optional live Jira sample data seeding (stories/subtasks/sprint). |
| scenarios/scrum-master/src/scripts/seed-helper-roster.ts | Seeds HelperRoster list rows used by blocker chase. |
| scenarios/scrum-master/src/openai-config.ts | OpenAI/Azure OpenAI client configuration wiring for agents SDK. |
| scenarios/scrum-master/src/mock/jira-mock.ts | In-memory mutable mock sprint for offline demo mode. |
| scenarios/scrum-master/src/index.ts | Express host + internal endpoints + scheduler kickoff. |
| scenarios/scrum-master/src/handlers/warn.ts | Nightly sprint risk detection + SM DM. |
| scenarios/scrum-master/src/handlers/sprint-summary.ts | T-2 mid-sprint markdown summary report builder + delivery. |
| scenarios/scrum-master/src/handlers/reconcile.ts | Deterministic status classifier + safe Jira transitions + SM approval card. |
| scenarios/scrum-master/src/handlers/config.ts | /config channel capture of channel ConversationReference. |
| scenarios/scrum-master/src/handlers/commands.ts | Slash-command router for scenario commands. |
| scenarios/scrum-master/src/handlers/answer.ts | Grounded Jira Q&A agent with per-user rolling history. |
| scenarios/scrum-master/src/cron/local-scheduler.ts | In-process node-cron scheduler for dev mode. |
| scenarios/scrum-master/src/config.ts | Central env-var parsing for scenario configuration. |
| scenarios/scrum-master/src/client.ts | OpenAI Agents + MCP tool registration + observability scope wrapper. |
| scenarios/scrum-master/src/cards/transition-confirm.card.ts | Adaptive Card for SM approval of non-safe Jira transitions. |
| scenarios/scrum-master/src/cards/standup-summary.card.ts | Adaptive Card for consolidated standup summary posting. |
| scenarios/scrum-master/src/cards/standup-request.card.ts | Adaptive Card DM for standup collection + “submitted” confirmation. |
| scenarios/scrum-master/src/cards/meeting-propose.card.ts | Adaptive Card for SM meeting-slot selection and booking. |
| scenarios/scrum-master/src/cards/blocker-escalation.card.ts | Adaptive Card DM escalating a blocker to the SM. |
| scenarios/scrum-master/src/agent.ts | AgentApplication wiring: message routing, card submits, notifications, token preload. |
| scenarios/scrum-master/package.json | Scenario package metadata, dependencies, and scripts. |
| scenarios/scrum-master/manifest/manifest.json | Teams manifest template for the scenario. |
| scenarios/scrum-master/manifest/agenticUserTemplateManifest.json | Agentic user template manifest for the scenario. |
| scenarios/scrum-master/docs/sharepoint-schema.md | Human-readable SharePoint list schema reference. |
| scenarios/scrum-master/azure-functions/tsconfig.json | TS config for the Azure Functions sibling project. |
| scenarios/scrum-master/azure-functions/src/index.ts | Azure Functions timers that POST to agent internal endpoints. |
| scenarios/scrum-master/azure-functions/README.md | Azure Functions setup/run/deploy documentation. |
| scenarios/scrum-master/azure-functions/package.json | Azure Functions package metadata and deps. |
| scenarios/scrum-master/azure-functions/local.settings.sample.json | Local settings template for Functions (callback URL + token). |
| scenarios/scrum-master/azure-functions/host.json | Azure Functions host configuration. |
| scenarios/scrum-master/azure-functions/.gitignore | Ignores dist, node_modules, and local.settings.json secrets. |
| scenarios/scrum-master/.gitignore | Ignores build output, env files, MSAL cache, and logs. |
| scenarios/scrum-master/.env.template | Comprehensive env template covering OpenAI, Agent 365, Jira, SharePoint, scheduling. |
Comments suppressed due to low confidence (2)
scenarios/scrum-master/src/util/httpLogger.ts:50
- The response log line includes
res.config.urlverbatim, which may contain query strings. Strip?…/#…before logging.
scenarios/scrum-master/src/util/httpLogger.ts:60 - The error log line includes
cfg?.urlverbatim, which may contain query strings. Strip?…/#…before logging.
Applies 7 of the 8 review comments; the 8th (client.ts:140 MCP transport close-in-finally) is deferred because it changes runtime behaviour on a codepath inherited from the base sample. * config.ts — default JIRA_MODE to 'mock' (was 'live') so a fresh clone runs offline in mock mode as the README documents, instead of throwing on missing Jira credentials. * index.ts — remove duplicate unhandledRejection/uncaughtException handlers (kept the more-detailed pair at top of file). * index.ts — emit a one-shot boot warning when INTERNAL_TRIGGER_TOKEN is empty so production deployments notice open /api/internal/* endpoints. * token-cache.ts — replace 'All rights reserved' header with the repo-standard MIT license header. * client.ts — rename observability service from 'TypeScript Claude Sample Agent' to 'Scrum Master Sample Agent'. * client.ts — fix fallback agentId/agentName (was 'typescript-compliance-agent' / 'TypeScript Compliance Agent'). * util/httpLogger.ts — add safePath() helper and strip query strings + URL fragments from request/response/error log lines so credentials in ?token=… never leak. * AGENT-CODE-WALKTHROUGH.md — sync doc snippet to new agentId strings.
|
Thanks copilot-pull-request-reviewer for the thorough pass. Pushed
Deferred: comment #7 (
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 59 out of 63 changed files in this pull request and generated 8 comments.
Comments suppressed due to low confidence (4)
scenarios/scrum-master/src/services/sharepoint.ts:127
- findByField() has the same issue as findByTitle(): URL-encoding the value inside the OData literal can break matches (e.g., spaces become %20). Encode the entire $filter expression and keep escapeODataString limited to OData single-quote escaping.
scenarios/scrum-master/src/client.ts:139 - invokeAgent() always closes MCP servers in finally. In this same scenario, calendar.ts documents that closing Streamable-HTTP MCP transports can break subsequent calls with "Session not found" because McpToolRegistrationService can reuse an already-closed transport. Consider not closing MCP servers here (or only closing on process shutdown) to avoid intermittent MCP failures across turns.
await this.closeServers();
scenarios/scrum-master/azure-functions/README.md:24
- This second link to
../openai/sample-agentis also incorrect for this scenario (the agent is..). Updating it avoids sending readers to a non-existent path.
- The agent already running somewhere reachable — either a dev tunnel URL, App Service, or Container Apps endpoint that exposes `/api/internal/*` from [`../openai/sample-agent`](../openai/sample-agent).
scenarios/scrum-master/src/index.ts:94
- When INTERNAL_TRIGGER_TOKEN is empty, /api/internal/* endpoints become unauthenticated even if the server is bound to 0.0.0.0 (non-development). Logging a warning is easy to miss in deployment logs; it's safer to reject requests unless you're explicitly in development mode.
| const graph = getGraphClient(); | ||
| const res = await graph | ||
| .api(`/sites/${siteId}/lists/${listName(key)}/items?$expand=fields&$filter=fields/Title eq '${escapeODataString(title)}'`) | ||
| .header('Prefer', 'HonorNonIndexedQueriesWarningMayFailRandomly') | ||
| .get(); |
| // inside our filter value as URL syntax. Our IDs use `#` as a separator (e.g. | ||
| // `<sprintId>#<yyyy-mm-dd>`), which without encoding gets treated as a URL fragment | ||
| // and truncates the filter — silently returning zero rows. | ||
| return encodeURIComponent(s.replace(/'/g, "''")); |
| export function getWarnConfig(): WarnConfig { | ||
| return { | ||
| todoPct: Number(optional('WARN_TODO_PCT', '0.40')), | ||
| sprintProgressPct: Number(optional('WARN_SPRINT_PROGRESS_PCT', '0.50')), | ||
| }; | ||
| } |
| scopes: getObservabilityAuthenticationScope() | ||
| }); | ||
|
|
||
| console.log(`Preloaded Observability token for agentId=${agentId}, tenantId=${tenantId} token=${aauToken?.token?.substring(0, 10)}...`); |
| const response = await client.invokeAgentWithScope( | ||
| `You have received the following email. Please follow any instructions in it. ${emailContent}` | ||
| ); |
| }, | ||
| "devDependencies": { | ||
| "@microsoft/m365agentsplayground": "^0.2.18", | ||
| "@types/express": "^4.17.21", |
| @@ -0,0 +1,121 @@ | |||
| # Scrum Master Assistant — Azure Functions | |||
|
|
|||
| Two timer-triggered functions that drive the SMA scheduled ceremonies. The agent process (in [`../openai/sample-agent`](../openai/sample-agent)) owns all state, tokens, and Adaptive Card logic — these functions only *nudge* it via HTTP. | |||
| /** | ||
| * Simple custom in-memory token cache with expiration handling | ||
| * In production, use a more robust caching solution like Redis | ||
| */ |
cce56fe to
aa27069
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 59 out of 63 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (2)
scenarios/scrum-master/src/agent.ts:238
- The prompt
Please follow any instructions in itmakes the agent treat email content as executable instructions, which is a classic prompt-injection vector. Even with general safety guidance elsewhere, it’s better to frame email content as untrusted data and ask for summarization + a drafted response.
// Then process the email
const response = await client.invokeAgentWithScope(
`You have received the following email. Please follow any instructions in it. ${emailContent}`
);
scenarios/scrum-master/src/client.ts:199
closeServers()closes every MCP server after each agent invocation. This conflicts with the calendar MCP guidance inservices/calendar.ts/ README troubleshooting: streamable HTTP MCP sessions are intended to be long-lived, and closing can lead to-32001 Session not foundon subsequent turns when the registration service reuses a closed transport.
private async closeServers(): Promise<void> {
if (this.agent.mcpServers && this.agent.mcpServers.length > 0) {
for (const server of this.agent.mcpServers) {
await server.close();
}
}
}
| const internalToken = getInternalTriggerToken(); | ||
| if (!internalToken) { | ||
| // Loud one-shot warning so production deployments don't accidentally ship | ||
| // open internal endpoints. Dev convention still allows an empty token so a | ||
| // solo developer can curl the endpoints without setting a secret. | ||
| console.warn( | ||
| '[security] INTERNAL_TRIGGER_TOKEN is empty — /api/internal/* is UNAUTHENTICATED. ' + | ||
| 'Set INTERNAL_TRIGGER_TOKEN in .env before deploying anywhere non-local.', | ||
| ); | ||
| } | ||
| function requireInternalToken(req: express.Request, res: Response): boolean { | ||
| const provided = req.get('x-internal-token') ?? ''; | ||
| if (!internalToken) { | ||
| // Convention: empty token in .env => endpoint is open (dev-only). The one-shot | ||
| // warning above logs this condition; each request stays quiet. | ||
| return true; | ||
| } | ||
| if (provided !== internalToken) { | ||
| res.status(401).json({ error: 'invalid internal token' }); | ||
| return false; | ||
| } | ||
| return true; | ||
| } |
| const adapter = agentApplication.adapter as CloudAdapter; | ||
| const botAppId = getBotAppId(); | ||
| try { | ||
| // Cast: stored references always come from `activity.getConversationReference()` | ||
| // which returns a fully-populated ConversationReference, but our storage layer | ||
| // deserializes as `Partial<>`. Adapter throws at runtime on truly-incomplete refs. | ||
| await adapter.continueConversation(botAppId, reference as ConversationReference, async (context) => { | ||
| await logic(context); | ||
| }); |
| type: 'Input.Toggle', | ||
| id: `approve_${t.issueKey}`, | ||
| title: 'Approve this change', | ||
| value: 'true', |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 59 out of 63 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (6)
scenarios/scrum-master/src/agent.ts:191
- Logging even a token prefix can leak credentials via console logs (often shipped to centralized log stores). Avoid printing any part of the observability token; log only metadata like agentId/tenantId.
console.log(`Preloaded Observability token for agentId=${agentId}, tenantId=${tenantId} token=${aauToken?.token?.substring(0, 10)}...`);
scenarios/scrum-master/src/agent.ts:238
- This prompt tells the model to "follow any instructions" inside an email, which is a direct prompt-injection vector (email content is untrusted). Ask the model to summarize/extract action items instead, without executing instructions from the email body.
const response = await client.invokeAgentWithScope(
`You have received the following email. Please follow any instructions in it. ${emailContent}`
);
scenarios/scrum-master/src/client.ts:140
- This closes MCP server sessions after every run(). In this same scenario (see calendar.ts), closing Streamable-HTTP MCP transports can break subsequent tool calls with "Session not found" because McpToolRegistrationService may reuse the underlying transport across agents. Consider keeping MCP sessions long-lived here as well.
} finally {
await this.closeServers();
}
scenarios/scrum-master/src/index.ts:94
- If INTERNAL_TRIGGER_TOKEN is empty, /api/internal/* becomes unauthenticated for all environments (including production misconfig). It would be safer to only allow the "open" behavior in development and fail closed otherwise.
scenarios/scrum-master/azure-functions/README.md:3 - This README links to
../openai/sample-agent, but the agent process for this scenario is the parentscenarios/scrum-masterpackage (and its README). The current link is broken/misleading in this repo layout.
Two timer-triggered functions that drive the SMA scheduled ceremonies. The agent process (in [`../openai/sample-agent`](../openai/sample-agent)) owns all state, tokens, and Adaptive Card logic — these functions only *nudge* it via HTTP.
scenarios/scrum-master/azure-functions/README.md:24
- Same as above: this link points at
../openai/sample-agent, but the internal endpoints live in the parent scenario package. Update the reference so readers can find the right code/docs.
- The agent already running somewhere reachable — either a dev tunnel URL, App Service, or Container Apps endpoint that exposes `/api/internal/*` from [`../openai/sample-agent`](../openai/sample-agent).
| scope.recordInputTokens(45); | ||
| scope.recordOutputTokens(78); |
| console.log('[OpenAI Config] Using Azure OpenAI'); | ||
| console.log(`[OpenAI Config] Endpoint: ${process.env.AZURE_OPENAI_ENDPOINT}`); | ||
| console.log(`[OpenAI Config] Deployment: ${process.env.AZURE_OPENAI_DEPLOYMENT}`); |
The MCP calendar tool loop occasionally succeeds at creating the event but the LLM returns a garbled response (e.g. webLink="onlineMeetingUrl" as a literal string, or id="errorEvent verification failed..." from a mis-formed rule-microsoft#6 error path). The existing !out.webLink check misses this because the values are truthy strings that pass Zod validation. * services/calendar.ts — after the existing empty-webLink guard, check that webLink is actually a URL (^https?://) and that id doesn't start with "error". On mismatch, throw a distinct CALENDAR_VERIFY_FAILED: error with an actionable "verify in Outlook" message. * handlers/chase.ts — catch the CALENDAR_VERIFY_FAILED: prefix separately; mark the blocker as booked (event was almost certainly created) and DM the SM a warning that asks them to verify in Outlook, instead of the scary "couldn't book the meeting" error. Tested in the POC with real MCP calendar output that reproduced the shape described.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 59 out of 63 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (7)
scenarios/scrum-master/src/agent.ts:191
- This log line prints a prefix of the exchanged observability token. Even partial token material can be used for correlation/attacks and shouldn’t be emitted to logs.
console.log(`Preloaded Observability token for agentId=${agentId}, tenantId=${tenantId} token=${aauToken?.token?.substring(0, 10)}...`);
scenarios/scrum-master/src/agent.ts:238
- The email-notification flow explicitly instructs the model to “follow any instructions” found in the email content. That creates a prompt-injection path where untrusted email content can drive unintended tool use or actions. The agent should treat email as untrusted data: summarize it, extract requests, and ask for user confirmation before acting.
// Then process the email
const response = await client.invokeAgentWithScope(
`You have received the following email. Please follow any instructions in it. ${emailContent}`
);
scenarios/scrum-master/src/token-cache.ts:37
- The comments describe “expiration handling” and “store a token with expiration”, but the implementation is a plain Map with no TTL/expiry logic. This is misleading for readers and for anyone reusing the sample code.
scenarios/scrum-master/src/client.ts:199 - This closes all MCP server transports after every agent invocation. In this scenario, calendar.ts documents that closing a Streamable-HTTP MCP transport can break subsequent tool calls with “Session not found” when McpToolRegistrationService reuses the closed transport across turns. To avoid intermittent failures, consider not closing MCP servers here (or ensure each agent gets a non-shared transport instance).
private async closeServers(): Promise<void> {
if (this.agent.mcpServers && this.agent.mcpServers.length > 0) {
for (const server of this.agent.mcpServers) {
await server.close();
}
}
}
scenarios/scrum-master/src/handlers/report.ts:268
- The action-items table hardcodes the target sprint as "Sprint 2". That will be incorrect for most real sprint names; use a neutral label like "Next sprint" (or derive it from sprint metadata if available).
scenarios/scrum-master/src/handlers/chase.ts:17 - The header comment describes the blocker-chase flow as using direct Graph calls (/me/findMeetingTimes, POST /events). The implementation now drives mcp_CalendarTools via services/calendar.ts, so this comment is outdated/misleading.
scenarios/scrum-master/src/handlers/report.ts:260 - The action-items table hardcodes the target sprint as "Sprint 2". That will be incorrect for most real sprint names (and even for mock data) and can confuse readers; consider using a neutral label like "Next sprint".
This issue also appears on line 268 of the same file.
| const internalToken = getInternalTriggerToken(); | ||
| if (!internalToken) { | ||
| // Loud one-shot warning so production deployments don't accidentally ship | ||
| // open internal endpoints. Dev convention still allows an empty token so a | ||
| // solo developer can curl the endpoints without setting a secret. | ||
| console.warn( | ||
| '[security] INTERNAL_TRIGGER_TOKEN is empty — /api/internal/* is UNAUTHENTICATED. ' + | ||
| 'Set INTERNAL_TRIGGER_TOKEN in .env before deploying anywhere non-local.', | ||
| ); | ||
| } | ||
| function requireInternalToken(req: express.Request, res: Response): boolean { | ||
| const provided = req.get('x-internal-token') ?? ''; | ||
| if (!internalToken) { | ||
| // Convention: empty token in .env => endpoint is open (dev-only). The one-shot | ||
| // warning above logs this condition; each request stays quiet. | ||
| return true; | ||
| } | ||
| if (provided !== internalToken) { | ||
| res.status(401).json({ error: 'invalid internal token' }); | ||
| return false; | ||
| } | ||
| return true; | ||
| } |
Ports the observability pattern from PR microsoft#333 (Chief-of-Staff) so both scenarios share the same MAC Activity wiring convention. Also addresses the five outstanding Copilot review comments from Jul 27 and Jul 29. Observability: * src/observability.ts (new) — shared module: getAgentId (agentic instance), getAgentBlueprintId (metadata only), getTenantId, buildAgentDetails, buildRequest, ensureObservabilityToken (both identities), runWithObservabilityContext. Runtime agent id comes from activity.recipient.agenticAppId — not the blueprint id — which is what the exporter needs for spans to bind to MAC Activity. * src/agent.ts — call ensureObservabilityToken at the top of handleAgentMessageActivity so per-turn spans have identity. * src/services/proactive.ts — wrap continueConversation in runWithObservabilityContext so proactive DMs and channel posts carry the same tenant+agent baggage as inbound /api/messages turns. * .env.template — add agent365Observability__agentInstanceId with guidance distinguishing runtime instance vs blueprint id. Review-comment fixes: * src/index.ts — fail-shut on empty INTERNAL_TRIGGER_TOKEN when NODE_ENV=production (was warn-only). Keeps dev convention intact. * src/services/proactive.ts — getBotAppId throws with a clear message when connections__service_connection__settings__clientId is unset instead of letting continueConversation fail with an opaque error. * src/cards/transition-confirm.card.ts — approval toggles default to 'false' so risky/non-whitelisted transitions require explicit opt-in. * src/client.ts — remove hard-coded 45 / 78 input/output token counts that were leaking into telemetry. Instrumentor populates real values. * src/openai-config.ts — mask AZURE_OPENAI_ENDPOINT to scheme+host and log only [set]/(unset) for the deployment so internal resource names never appear in logs.
|
Updated in 1. Wired A365 observability (matches PR #333 pattern)Ran the
2. Addressed the 5 open review comments
Not yet in this commit
Verification pending on our tenant — will run the agent locally and confirm the boot log shows both identities being warmed. |
Two things landed after live-tenant verification with the a365-skills
'a365-code-validator' + 'instrument-observability' flow:
Observability (from running the instrument-observability skill against
the sample):
* src/observability.ts — add withInvokeAgentScope() and
withExecuteToolScope() helpers plus a per-turn AsyncLocalStorage so
tool definitions can pick up the current TurnContext without threading
it through every signature. buildUserDetails / buildCallerDetails
populate user.id / userName / userEmail (UPN only when the from.id
actually carries one). runWithTurnBaggage() replaces the old
runWithObservabilityContext export.
* src/agent.ts — hoist ensureObservabilityToken +
preloadObservabilityToken above the dispatch decision so cards,
slash commands, Answer, and the generic LLM path all run inside one
identity-tagged baggage scope. Same treatment for the
agent-notification (email) branch.
* src/client.ts — bind AgentDetails / Request via buildAgentDetails() /
buildRequest() from the shared module, so gen_ai.agent.id gets the
runtime agentic instance id rather than the blueprint id. Drops the
hard-coded conversationId literal.
* src/handlers/answer.ts — wrap the Q&A turn in withInvokeAgentScope so
MAC Activity gets an invoke_agent parent above the auto-instrumented
chat inference span.
* src/services/jira-tool.ts — wrap jira_get_issue,
jira_list_sprint_issues, jira_get_issue_comments in
withExecuteToolScope so tool calls become named execute_tool spans
bound to the agent identity.
* .env.template — small doc tweak clarifying the exporter default.
Bug fix (found while live-testing the Q&A path):
* src/services/issue-labels.ts — toJiraKey() checked the canonical
^PROJ-N regex before the taskish ^task-N regex, so 'Task-9' matched
the canonical branch and returned 'TASK-9' (a made-up project). Only
bare numbers reached the correct branch. Reorder so the task/# prefix
match runs first; canonical falls through only for real project keys
like 'DEMO-14' or 'EDP-10'.
Ignore hygiene:
* .gitignore — exclude TS compile artefacts under src/**/*.{js,d.ts,
js.map}, azure-functions/src/**/*.{js,d.ts,js.map}, and the workspace-
local skill install directories (.agents/, .claude/,
.github/copilot-instructions.md) so those don't accidentally join the
next commit.
Live-tenant verification highlights (agentId f5a59964-... on tenant
1fe41f4b-...):
* Boot: Agent365Exporter enabled: true, both blueprint + agentic-
instance identity tokens cached.
* MVP 1 standup: sentTo=4, per-card submit round-trip succeeded; span
export-group succeeded in ~1.3s (9/10 spans bound on Answer turn,
6/10 on standup fan-out).
* MVP 3 chase: end-to-end unblock meeting booked; 4/4 spans bound on
the calendar tool loop.
* MVP 4 warn / MVP 6 mid-sprint RAG / MVP 7 sprint close: all fired
through HTTP triggers, all export-group succeeded.
* MVP 5 Q&A: withInvokeAgentScope + withExecuteToolScope both fire and
bind. Task-9 lookup succeeds after the label fix.
* M365 admin center Agents view now shows the Scrum Master Blueprint
with a MarioR@... session on today's date.
|
Live-tenant verification complete. Ran the Observability instrumentation is now in placeThe skill flow surfaced 5 gaps in what we had before, all fixed in this commit:
Bug fix caught during live testing
Live-tenant verification resultsTenant
M365 admin center Agents view now shows the Scrum Master Blueprint with Known cosmetic warnings (not from this change, pre-existing)
Worth filing as follow-up issues on the samples repo but neither blocks this PR. Not verified in this session (deferred, not blocking)
|
Add scrum-master scenario: Scrum Master autopilot using Node.js SDK
What this adds
A scenario extension under
scenarios/scrum-master/— an autonomous Scrum Master built on the Microsoft Agent 365 SDK + OpenAI Agents SDK that runs a scrum team's ceremonies end-to-end: daily standups, board reconciliation, blocker chase, mid-sprint risk warnings, grounded Q&A, and the sprint close report. Complements the existing Chief-of-Staff scenario (#333) by covering the delivery-team side of leader / squad workflows.Capabilities
Seven flows, each a handler in
src/handlers/:SMA_HelperRoster→ MCP calendar tool books an unblock meeting on the agent's mailboxFull per-flow mermaid sequence diagrams:
scenarios/scrum-master/docs/design.md.Design principles
Verbatim from
docs/design.md:setImmediate.SMA_*lists survive restarts.What's included
Runs offline
JIRA_MODE=mock(the default) drives the sample against a mutable in-memory Jira sprint insrc/mock/jira-mock.ts. Contributors can clone,npm install,npm run dev,npm run test-tooland try/standupin the Agents Playground without an Atlassian account, without a SharePoint site, and without a Teams tenant.Live mode (
JIRA_MODE=live) requires:Auth model — delegated only
All Microsoft Graph calls use MSAL device-code + delegated scopes (
Sites.ReadWrite.All,Sites.Manage.All,Files.ReadWrite.All). Onboarding is a single interactive sign-in. No application-permission client credentials, no admin consent, no Teams policies to provision.Verification
00000000-…,<your-org>.atlassian.net,alice@contoso.cometc.Placement — why
scenarios/Placed at the repo root under
scenarios/scrum-master/rather than under a language tier (e.g.nodejs/openai/) because a scrum-master workflow doesn't map cleanly to a single SDK — it composes Agent 365 SDK, OpenAI Agents SDK, MCP Calendar, and multiple external services. This mirrors how the Chief-of-Staff sample (#333) is placed. Happy to relocate tonodejs/openai/scenarios/scrum-master/if reviewers prefer a language-tier scoping.Not included / future work
SMA_Teamslist +TeamIdcolumn + per-team Jira credentials in Key Vault is documented indocs/design.md..mstoken-cache.json, gitignored). Production swap-in is called out indocs/design.md.tests/e2e/. Recommend follow-up PR once initial review lands.Commits
d33aecc— initial import of the scenario code (57 files)feb98d9— Jira sample-data seed script + SharePoint schema doc1c5a4af— README rewrite (intro-features-prereqs-setup structure)62b0cad— observability + docs polish (logger, http-logger, startup banner, process safety nets, design.md, TOC, troubleshooting, deploy-to-Azure)Related
nodejs/openai/sample-agent