Repository navigation
Add chief-of-staff scenario: Chief of Staff autopilot using Node.js SDK #333
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Rick Brighenti (rbrighenti)
merged 37 commits into
microsoft:main
from
Yogeshp-MSFT:Chief-of-Staff
Nov 6, 2026
Merged
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
6924afa
Add chief-of-staff scenario: Chief of Staff Teammate for Node.js
Yogeshp-MSFT 0b08498
Address Copilot review comments (safe fixes only)
Yogeshp-MSFT 8d96da2
Address second round of Copilot review comments (safe fixes only)
Yogeshp-MSFT 3cbf794
fix(chief-of-staff): snapshot READY payload before markCaptureComplet…
Yogeshp-MSFT 1eb0656
fix(chief-of-staff): wire A365 observability so MAC Activity tab popu…
Yogeshp-MSFT f71e0e6
refactor(chief-of-staff): shared observability module + real UPN in M…
Yogeshp-MSFT 93c9119
refactor(chief-of-staff): sync latest cos-agent — obs polish + card/s…
Yogeshp-MSFT 27be9a4
fix(chief-of-staff): secure direct actions and WorkIQ auth
Yogeshp-MSFT 928b75a
Update scenarios/chief-of-staff/README.md
Yogeshp-MSFT 8f2d131
fix(chief-of-staff): address review feedback (authz, reliability, set…
Yogeshp-MSFT ea7e17a
fix(chief-of-staff): address second review round (access, delivery, CI)
Yogeshp-MSFT 1a8a319
Update scenarios/chief-of-staff/.env.template
Yogeshp-MSFT c0998b6
fix(chief-of-staff): address third review round (privacy, idempotency…
Yogeshp-MSFT 4350f05
Merge branch 'main' of microsoft/Agent365-Samples into Chief-of-Staff
Yogeshp-MSFT 0ba559b
fix(chief-of-staff): address fourth review round (capture integrity, …
Yogeshp-MSFT 9066415
fix(chief-of-staff): address fifth review round (plan boundary, captu…
Yogeshp-MSFT be007e8
fix(chief-of-staff): address sixth review round (summary isolation, d…
Yogeshp-MSFT d3a2c7f
fix(chief-of-staff): bind MCP writes and address latest review
Yogeshp-MSFT eb15cbc
Update scenarios/chief-of-staff/package.json
Yogeshp-MSFT ee188f6
Update scenarios/chief-of-staff/README.md
Yogeshp-MSFT 50a4478
fix(chief-of-staff): preserve resolved due dates and resume capture a…
Yogeshp-MSFT 8d6bd40
fix(chief-of-staff): reconcile extension proposals and escalation state
Yogeshp-MSFT d04ec88
Update scenarios/chief-of-staff/README.md
Yogeshp-MSFT fee83ca
Fixed New PR comments as of 23-10-2026
Yogeshp-MSFT e25f6b6
Default observability exporter to false in .env.template
Yogeshp-MSFT 4a57c75
fix(chief-of-staff): bound Graph calls and preserve safe extension de…
Yogeshp-MSFT e0a683a
fix(chief-of-staff): upgrade MSAL past authorization flow advisory
Yogeshp-MSFT b7f4900
fix(chief-of-staff): bound briefs, capture titles, and remaining polls
Yogeshp-MSFT a56e53c
fix(chief-of-staff): finalize review fixes and bootstrap login recovery
Yogeshp-MSFT 9adbbf1
fix(chief-of-staff): require exact extension decision replies
Yogeshp-MSFT e392f0b
fix(chief-of-staff): serialize reassignment and compare secret expiry…
Yogeshp-MSFT 634d966
fix(chief-of-staff): include leader calendar exclusions and correct c…
Yogeshp-MSFT 6a6ecb7
fix(chief-of-staff): invalidate obsolete capture artifacts and serial…
Yogeshp-MSFT 1007319
fix(chief-of-staff): guard reinvited captures and extension proposal …
Yogeshp-MSFT ff7e1cf
fix(chief-of-staff): route deterministic card actions without WorkIQ …
Yogeshp-MSFT 64ec196
fix(chief-of-staff): prefer live calendar occurrence boundaries
Yogeshp-MSFT b6affe0
Merge branch 'main' into Chief-of-Staff
rbrighenti File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # Copyright (c) Microsoft Corporation. | ||
| # Licensed under the MIT License. | ||
|
|
||
| name: CI - Build Chief of Staff Scenario | ||
|
|
||
| on: | ||
| workflow_call: # Called by orchestrator | ||
| workflow_dispatch: # Manual trigger | ||
|
|
||
| jobs: | ||
| scenario-chief-of-staff: | ||
| name: Chief of Staff Scenario | ||
| permissions: | ||
| contents: read | ||
| runs-on: ubuntu-latest | ||
| defaults: | ||
| run: | ||
| working-directory: ./scenarios/chief-of-staff | ||
|
|
||
| strategy: | ||
| matrix: | ||
| node-version: ['20', '22'] | ||
|
|
||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Node.js ${{ matrix.node-version }} | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: ${{ matrix.node-version }} | ||
|
|
||
| - name: Install dependencies | ||
| run: npm install | ||
|
|
||
| - name: Build and run regression tests | ||
| run: npm test | ||
|
|
||
| - name: Run offline bootstrap login tests | ||
| shell: pwsh | ||
| run: ./tests/bootstrap-graph-app.test.ps1 |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,229 @@ | ||
| # ─── A365 platform / server ─── | ||
| # NODE_ENV and HOST below are LOCAL-DEV values. Do not copy them to Azure App | ||
| # Service: set NODE_ENV=production there (static BEARER_TOKEN_* auth is only | ||
| # accepted when NODE_ENV=development), omit HOST (or set HOST=0.0.0.0), and | ||
| # let App Service supply PORT. | ||
| NODE_ENV=development | ||
| HOST=127.0.0.1 | ||
| PORT=3978 | ||
| DEBUG=agents:* | ||
|
|
||
| # ─── Logging ─── | ||
| # LOG_LEVEL: error | warn | info | debug | trace (case-insensitive, default=info) | ||
| # LOG_HTTP: when 'true', prints every outgoing Graph/HTTP call with status + latency (secrets redacted) | ||
| LOG_LEVEL=info | ||
| LOG_HTTP=false | ||
|
|
||
| # Default-off safety gate. When true, authenticated direct Teams requests may | ||
| # invoke action tools (for example, send email or book a meeting). Instructions | ||
| # embedded in email, documents, transcripts, web content, or tool results stay untrusted. | ||
| ALLOW_DIRECT_USER_ACTIONS=false | ||
|
|
||
| # ─── Agent identity (from `a365 develop setup`) ─── | ||
| agent_id= | ||
| # Agentic auth toggle. `true` uses the agentic OBO exchange to get a | ||
| # per-server token for each WorkIQ MCP server (production). `false` reads | ||
| # per-server dev tokens BEARER_TOKEN_<MCP_SERVER_NAME> (see the MCP section | ||
| # below) — local development only: it is refused unless NODE_ENV=development, | ||
| # and any BEARER_TOKEN_* left set while this is `true` is rejected as a | ||
| # configuration error rather than silently used. Leave `true` for the | ||
| # reference deployment. | ||
| USE_AGENTIC_AUTH=true | ||
| connections__service_connection__settings__clientId= | ||
| connections__service_connection__settings__clientSecret= | ||
| connections__service_connection__settings__tenantId= | ||
| connections__service_connection__settings__scopes=5a807f24-c9de-44ee-a3a7-329e88a00ffc/.default | ||
| connectionsMap__0__serviceUrl=* | ||
| connectionsMap__0__connection=service_connection | ||
| agentic_type=agentic | ||
| agentic_connectionName=AgenticAuthConnection | ||
| agentic_altBlueprintConnectionName=service_connection | ||
| agentic_scopes=https://graph.microsoft.com/.default | ||
|
|
||
| # ─── WorkIQ MCP servers ─── | ||
| # Servers are read from ToolingManifest.json (deploy it next to dist/). Each | ||
| # server has its own audience and the Tools.ListInvoke.All scope; the agent | ||
| # acquires a separate agentic token per server — no shared MCP token. | ||
| # | ||
| # Dev only (USE_AGENTIC_AUTH=false and NODE_ENV=development): set one | ||
| # token per server, acquired for that server's audience | ||
| # (`a365 develop get-token`), e.g. | ||
| # BEARER_TOKEN_MCP_MAILTOOLS= | ||
| # BEARER_TOKEN_MCP_TEAMSSERVER= | ||
| # BEARER_TOKEN_MCP_CALENDARTOOLS= | ||
| # | ||
| # When `false` (default), a WorkIQ MCP registration failure fails the turn | ||
| # with a clear error. Set `true` only in constrained dev tenants where some | ||
| # MCP servers are expected to be unavailable — the agent then continues | ||
| # without those tools and logs a warning. | ||
| SKIP_TOOLING_ON_ERRORS=false | ||
|
|
||
| # ─── Foundry — Azure OpenAI (gpt-4o) ─── | ||
| AZURE_OPENAI_ENDPOINT= | ||
| AZURE_OPENAI_DEPLOYMENT=gpt-4o | ||
| AZURE_OPENAI_API_KEY= | ||
| AZURE_OPENAI_API_VERSION=2024-10-21 | ||
|
|
||
| # ─── Planner (single source of truth) ─── | ||
| # Two paths — you only need ONE. | ||
| # | ||
| # Path A (recommended, simplest): leave PLANNER_PLAN_ID + PLANNER_BUCKET_NEW | ||
| # blank and let the agent auto-resolve them from LEADERSHIP_TEAM_ID at | ||
| # runtime. Requirements: | ||
| # - LEADERSHIP_TEAM_ID is set (any format — GUID / display name / channel email) | ||
| # - The Team has exactly ONE Planner plan → auto-picked | ||
| # (or set PLANNER_PLAN_NAME to disambiguate when the Team has multiple) | ||
| # - The plan contains a bucket named "New" (case-insensitive) → auto-picked | ||
| # (or set PLANNER_BUCKET_NAME to override the search name) | ||
| # | ||
| # Path B (explicit): set both IDs directly. From the Planner URL + Graph Explorer: | ||
| # URL: …/plan/<PLAN_ID>/view/board | ||
| # GET /planner/plans/<PLAN_ID>/buckets → copy the "New" bucket id | ||
| PLANNER_PLAN_ID= | ||
| PLANNER_BUCKET_NEW= | ||
| # Optional overrides for Path A: | ||
| PLANNER_PLAN_NAME= | ||
| PLANNER_BUCKET_NAME=New | ||
|
|
||
| # ─── Leader ─── | ||
| # LEADER_AAD_ID auto-resolves from LEADER_UPN on first turn — leave blank if you like. | ||
| LEADER_UPN= | ||
| LEADER_AAD_ID= | ||
| # Display name used in card copy (e.g. "Assigned by: Mario"). Optional — falls | ||
| # back to "the Leader" when unset. | ||
| LEADER_NAME= | ||
| # ─── Standalone Graph worker app (REQUIRED) ─── | ||
| # EVERY Graph API call (Planner, people, calendar, transcripts, insights) | ||
| # uses this app's client credentials (application permissions). There is no | ||
| # agentic OBO fallback — the agent reports a clear error if these are blank. | ||
| # Create them with scripts/bootstrap-graph-app.ps1 (README §3b). | ||
| GRAPH_APP_ID= | ||
| GRAPH_APP_SECRET= | ||
| GRAPH_TENANT_ID= | ||
|
|
||
| # ─── Team access control (Recall gate) ─── | ||
| # The leadership Team. This is the access boundary for Planner/calendar data | ||
| # (Teams chat AND email turns). Accepts any of: | ||
| # * M365 Group GUID e.g. 44db7598-1234-abcd-… (recommended) | ||
| # * Teams channel email e.g. 44db7598.contoso.onmicrosoft.com@amer.teams.ms | ||
| # * Team display name e.g. Leadership Operations | ||
| # The last two are resolved to the GUID on first turn and cached for the | ||
| # process lifetime; if more than one group matches, the agent refuses to | ||
| # guess and you must set the GUID. If blank, Recall is open to any sender the | ||
| # agent can identify as a tenant user (unidentified email senders are always | ||
| # restricted). When set, the gate fails closed: if the Team can't be resolved | ||
| # or membership can't be read, non-leader senders are treated as non-members. | ||
| LEADERSHIP_TEAM_ID= | ||
|
|
||
| # ─── In-process scheduler (cron + pollers) ─── | ||
| SCHEDULER_ENABLED=true | ||
| # Gate for the daily Brief cron. `true` = Brief card fires per CRON_BRIEF. | ||
| # `false` = silenced (all other crons still run). Default in code is false. | ||
| BRIEF_ENABLED=false | ||
| CRON_BRIEF=0 8 * * 1-5 | ||
| # Follow-up check-ins. Each tick also runs the deterministic stale-follow-up | ||
| # escalation sweep (no separate escalate cron). | ||
| CRON_FOLLOWUP=0 * * * * | ||
| # IANA time zone for CRON_* patterns above. If blank, patterns are interpreted | ||
| # in the SERVER's local time — which differs between local dev and Azure App | ||
| # Service (UTC). Set explicitly for reproducibility across environments. | ||
| # Examples: America/Los_Angeles, Asia/Kolkata, Europe/London, UTC | ||
| CRON_TIMEZONE= | ||
| # Meeting-capture orchestrator (calendar-driven — leader AND CoS on the invite) | ||
| POLL_MEETINGS_MS=60000 # 1 min — cheap; discovery + retry sweep per tick | ||
| POLL_TASKS_MS=300000 | ||
| # Hours to wait for the owner to reply to a follow-up card before escalating | ||
| # to the leader. Sweep runs at every follow-up cron tick. | ||
| FOLLOWUP_ESCALATE_AFTER_HOURS=3 | ||
| # Suppress a fresh check-in for the SAME owner within this window (hours) | ||
| # after they responded / resolved / had a meeting scheduled. Per-owner — | ||
| # reassignment clears the cooldown for the new owner. | ||
| FOLLOWUP_COOLDOWN_HOURS=4 | ||
| # Follow-up cycles to keep retrying an owner's extension/blocker request when | ||
| # the leader's card can't be delivered, before telling the owner to contact | ||
| # the leader directly. | ||
| # LEADER_DELIVERY_MAX_ATTEMPTS=24 | ||
|
|
||
| # ─── Meeting capture (transcripts + Copilot AI insights) ─── | ||
| # The CoS agent's inviteable UPN — used to filter calendar events. A meeting | ||
| # is captured whenever BOTH the leader and this UPN are on the invite (as | ||
| # organizer or attendee), regardless of who organized it. In-progress and | ||
| # upcoming meetings are tracked too; capture runs once the transcript exists. | ||
| COS_AGENT_UPN= | ||
| # Which user's Graph endpoints do we hit? Two modes: | ||
| # Both modes also read the leader's calendar for transcript exclusion | ||
| # boundaries; this does not change the mailbox used for artifact access. | ||
| # cos-agent (default) — we read /users/{COS_AGENT_UPN}/... — Teams | ||
| # application-access policy needs to be granted ONLY to the CoS agent | ||
| # UPN. Zero per-leader setup; any meeting with both the leader and the | ||
| # CoS on the invite gets captured. Works iff attendee-role access is | ||
| # sufficient for the transcript/insights endpoints in your tenant. | ||
| # leader — we read /users/{LEADER_UPN}/... — policy must be granted to | ||
| # each leader (or -Global). Guaranteed to work but per-leader setup. | ||
| CAPTURE_GRAPH_OWNER=cos-agent | ||
| # How far back to scan the leader's calendar each tick. | ||
| TRANSCRIPT_WATCH_HOURS=4 | ||
| # How far FORWARD to scan the calendar. We include upcoming/in-progress | ||
| # meetings so the transcript-fetch retry loop can start early and pick up | ||
| # transcripts the moment Teams publishes them — even for meetings the | ||
| # leader joined-and-left before their scheduled end. | ||
| TRANSCRIPT_WATCH_FORWARD_HOURS=24 | ||
| # Wait for Copilot AI insights: waitMinutes = clamp(durationMin * mult, min, max) | ||
| INSIGHTS_WAIT_MULTIPLIER=0.5 | ||
| INSIGHTS_MIN_WAIT_MINUTES=3 | ||
| INSIGHTS_MAX_WAIT_MINUTES=30 | ||
| # If transcript still isn't there after this many hours since meeting end, drop. | ||
| CAPTURE_GIVE_UP_AFTER_HOURS=4 | ||
| # Attempt count after which we consider transcript-only capture READY (skips | ||
| # the polite retry for Copilot insights). Default 2 — fires on second attempt | ||
| # if transcript is inlined and insights are still empty. Set to 1 for demo | ||
| # tenants where insights never arrive (fires on first attempt). | ||
| CAPTURE_MIN_ATTEMPTS_TRANSCRIPT_ONLY=2 | ||
|
|
||
| # ─── Display formatting ─── | ||
| # IANA time zone used for wall-clock text in Adaptive Cards (Brief, follow-up, | ||
| # extension, escalation, and assignment due dates) and for the blocker | ||
| # meeting slots (10:00 / 14:00 / 16:00 in this zone, DST-aware). Defaults to | ||
| # Asia/Kolkata for the demo tenant. Set to your leader's home TZ. | ||
| BRIEF_DISPLAY_TZ=Asia/Kolkata | ||
|
|
||
| # ─── State persistence (single-instance file-backed) ─── | ||
| # STATE_BACKEND=file — persist to STATE_DIR (default; survives restart) | ||
| # STATE_BACKEND=null — in-memory only (lost on restart; use for tests) | ||
| STATE_BACKEND=file | ||
| # Directory for state files. Local dev default: ./.cos-state | ||
| # Azure App Service (single-instance): set to /home/data/cos-state — /home | ||
| # is the per-app persistent volume mounted across restarts. | ||
| STATE_DIR=./.cos-state | ||
| # TTL for finished captures. In-flight (pending/ready) records are always | ||
| # kept; complete/gave-up records older than this many days are pruned on | ||
| # next hydration to keep the JSON file small. | ||
| CAPTURE_STATE_RETENTION_DAYS=30 | ||
| # TTL for terminal follow-ups (responded/resolved). In-flight (pending/ | ||
| # escalated) records are always kept. Set well above the cooldown window | ||
| # in cos/followup.ts so cooldowns survive restart. | ||
| FOLLOWUP_STATE_RETENTION_HOURS=72 | ||
|
|
||
| # ─── Observability ─── | ||
| ENABLE_A365_OBSERVABILITY_EXPORTER=false | ||
| A365_OBSERVABILITY_LOG_LEVEL=info | ||
| # The A365 exporter logger stays silent unless the OTel SDK logger is on too. | ||
| # Uncomment both to debug export failures. | ||
| # OTEL_LOG_LEVEL=INFO | ||
| # When `true`, the observability SDK uses a caller-supplied resolver for | ||
| # activity attribution instead of the default. Leave `false` unless you've | ||
| # wired a custom resolver. (Note: this key is intentionally mixed-case to | ||
| # match the observability SDK.) | ||
| Use_Custom_Resolver=false | ||
| # Runtime agent identity (the AGENTIC INSTANCE app id, not the blueprint). | ||
| # Normally left blank — it is read per turn from activity.recipient.agenticAppId. | ||
| # Set it only for a deployment where inbound activities don't carry it; a wrong | ||
| # value here makes every span unbindable and MAC Activity stays empty. | ||
| agent365Observability__agentInstanceId= | ||
| # NOTE: agent365Observability__agentId holds the BLUEPRINT id and is used only | ||
| # as span metadata + for warming the blueprint's observability token. | ||
| agent365Observability__agentId= | ||
| agent365Observability__agentName=Chief of Staff | ||
| agent365Observability__agentDescription=Chief of Staff | ||
| agent365Observability__tenantId= | ||
| agent365Observability__agentBlueprintId= | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| node_modules/ | ||
| dist/ | ||
| .env | ||
| .env.bak-* | ||
| .env.local | ||
| # Chat/session transcripts — capture raw env values including secrets. | ||
| chat.json | ||
| *.log | ||
| .DS_Store | ||
| # In-process state (pending captures, follow-ups, conversation refs, planner | ||
| # baseline). Regenerated at runtime — never commit. | ||
| .cos-state/ |
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.