Skip to content

Add chief-of-staff scenario: Chief of Staff autopilot using Node.js SDK - #333

Open
Yogeshp-MSFT (Yogeshp-MSFT) wants to merge 6 commits into
microsoft:mainfrom
Yogeshp-MSFT:Chief-of-Staff
Open

Add chief-of-staff scenario: Chief of Staff autopilot using Node.js SDK#333
Yogeshp-MSFT (Yogeshp-MSFT) wants to merge 6 commits into
microsoft:mainfrom
Yogeshp-MSFT:Chief-of-Staff

Conversation

@Yogeshp-MSFT

Copy link
Copy Markdown

Summary

Adds a new sample under scenarios/chief-of-staff/ — an autonomous Microsoft
Agent 365 teammate that runs a leader's operating rhythm. The agent lives in
Microsoft Teams, watches the leader's calendar for meetings it's been invited
to, captures action items and decisions from the transcript into Planner,
sends a daily Brief card, follows up with owners, books unblock meetings,
escalates non-responsive owners, and lets people close tasks by chat.

Built on @microsoft/agents-hosting + @openai/agents + Azure OpenAI gpt-4o.

What the sample demonstrates

Capability Trigger
Capture — extracts action items + decisions from Teams meetings into Planner tasks Calendar poller finds a leader-organised meeting the CoS was invited to → transcript fetched → LLM extracts
Daily Brief — Adaptive Card DM with priorities, watch items, upcoming meetings Cron (default 8am weekdays, gated by BRIEF_ENABLED)
Follow-up — Adaptive Card DM to each at-risk owner (On track / Need more time / I'm blocked) Cron (default hourly)
Extension request — leader gets approval card, agent PATCHes Planner due date Owner clicks "Need more time" or types extend
Blocker meeting — agent proposes 3 slots, books calendar invite on click Owner clicks "I'm blocked" or types blocked
Escalation — 🚨 card DM'd to the leader if the owner ignores their follow-up Post-followup sweep, auto-cancels if the task is closed first
Task complete (Planner UI) — leader gets a confirmation DM Planner poll detects percentComplete: 100
Task complete (chat) — owner tells the agent in plain language, agent PATCHes Planner + notifies leader Message router matches completion phrase + quoted title
Recall / chit-chat — leader asks "where are we on X?" and gets a status answer, restricted to leadership team members LLM turn with planner_list_tasks + mcp_CalendarTools

Everything is deterministic except the two flows that explicitly need
natural-language understanding (capture extraction + recall/chit-chat). All
routing, dedup, date math, and Planner writes are TypeScript.

Design highlights

  • Dual-identity auth — agentic OBO for Bot Framework side, standalone
    cos-graph-worker app for every Graph call. See DESIGN.md §7.
  • Idempotent cards — every Adaptive Card verb is dedup-guarded; the
    meeting-poll orchestrator uses an in-flight guard against overlapping ticks.
  • File-backed persistent statePersistentMap<V> survives restarts;
    in-flight captures and follow-ups don't need to be re-triggered.
  • Turnkey provisioningscripts/bootstrap-graph-app.ps1 creates the
    worker app, grants all 9 Graph app-role permissions, admin-consents them
    tenant-wide, and rotates a client secret in one command.

Files

  • 38 files total, all under scenarios/chief-of-staff/
  • ~30 TypeScript files across src/agent.ts, src/scheduler.ts, src/cards/, src/cos/, src/graph/, src/state/, src/util/
  • Docs: README.md (end-to-end setup) + DESIGN.md (architecture, per-flow sequence diagrams, complete env-var reference)
  • Scripts: scripts/bootstrap-graph-app.ps1 (Graph worker provisioning) + compare_grants.ps1 (diagnostic)
  • No committed: .env / .env.bak / chat.json / log.txt / a365.*.config.json / .cos-state/ / manifest/ / dist/ / node_modules/ — all covered by .gitignore (43 patterns)

Testing performed

  • Boot checknpm run build + npm run dev boot cleanly on Node 20 (Windows + Linux)
  • End-to-end verification — each of the eight flows in README.md §8
    tested against a fresh M365 tenant with Copilot license
  • Restart safety — verified via README.md §8.8 that already-processed
    meetings are not re-captured after a restart (state-file dedup)
  • tsc --noEmit — passes cleanly across all 30 TS files
  • License audit — 336 total npm packages, 333 with standard permissive
    licenses (MIT / Apache-2.0 / ISC / BSD / BlueOak / 0BSD); the only
    non-permissive is @microsoft/m365agentsplayground (Microsoft Pre-Release
    License), which is a devDep and identical to what the existing
    nodejs/openai/sample-agent uses

Reviewer notes

Folder location — I placed this at scenarios/chief-of-staff/ at repo root.
The existing convention in this repo is nodejs/openai/<sample>/,
dotnet/w365-computer-use/<sample>/, etc. Happy to move this under
nodejs/openai/chief-of-staff/ (or wherever fits best) if the current
location doesn't align with your intent for a "scenarios" umbrella. Let me
know and I'll git mv + amend + force-push.

Repo boilerplate — README ends with the standard Support / Contributing
/ Additional Resources / Trademarks / License sections, matching the
nodejs/openai/sample-agent template. SECURITY.md and LICENSE.md
links point at the repo-root files via ../../ from this sample folder.

Checklist

  • Follows the existing sample pattern (.env.template, README.md,
    DESIGN.md, package.json, tsconfig.json, ToolingManifest.json,
    scripts/, src/)
  • .gitignore present and comprehensive
  • No secrets, tenant IDs, or real user data in any committed file
  • All GUIDs in shipped files are Microsoft-well-known platform constants
    (Graph SP, Agent 365 Tools resource, Agent 365 Runtime scope) or
    <PLACEHOLDER> values
  • TypeScript compiles cleanly with tsc --noEmit
  • README §8 includes step-by-step verification for every capability
  • Two PowerShell scripts (bootstrap-graph-app.ps1, compare_grants.ps1)
    use <PLACEHOLDER> values, not real IDs

Autonomous Microsoft Agent 365 teammate that runs a leader's operating
rhythm. Captures decisions and action items from Teams meetings into
Planner, sends a daily Brief card, follows up with owners, books
unblock meetings, escalates non-responsive owners, and lets people
close tasks by chat.

Capabilities: Capture, Daily Brief, Follow-up, Extension request,
Blocker meeting, Escalation, Task complete (Planner + chat), Recall.

Deterministic TypeScript orchestration around gpt-4o (Azure OpenAI)
for capture extraction and recall chit-chat. Every other flow is
deterministic TypeScript with idempotent Adaptive Card verbs and
file-backed state that survives restarts.

See scenarios/chief-of-staff/README.md for end-to-end setup and
scenarios/chief-of-staff/DESIGN.md for architecture + per-flow
sequence diagrams.
Copilot AI lite review requested due to automatic review settings July 23, 2026 09:59
@Yogeshp-MSFT
Yogeshp-MSFT (Yogeshp-MSFT) requested a review from a team as a code owner July 23, 2026 09:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Node.js/TypeScript “Chief of Staff” scenario sample under scenarios/chief-of-staff/, showcasing an autonomous Agent 365 teammate that orchestrates meeting capture, Planner tasking, brief/follow-up cards, and proactive workflows in Teams, backed by Azure OpenAI and Graph.

Changes:

  • Introduces a full in-process runtime (Express endpoint + AgentApplication handlers + cron/poller scheduler) for capture/brief/follow-up/escalation/task-complete flows.
  • Adds Graph integration utilities (app-only token acquisition, Planner config auto-resolution, transcript/insights polling) plus file-backed persistent state.
  • Adds end-to-end documentation and tenant bootstrap scripts for provisioning a standalone Graph worker app and validating permissions.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
scenarios/chief-of-staff/README.md End-to-end setup, verification flows, troubleshooting, deployment guidance.
scenarios/chief-of-staff/DESIGN.md Architecture/design details for the scenario sample.
scenarios/chief-of-staff/.env.template Full environment-variable template for local/dev/prod configuration.
scenarios/chief-of-staff/.gitignore Excludes secrets, build output, manifests, logs, and persisted runtime state.
scenarios/chief-of-staff/package.json Node scripts + dependencies (Agents SDK, A365 extensions, Graph/MSAL, cron, express).
scenarios/chief-of-staff/tsconfig.json TypeScript compiler configuration for building to dist/.
scenarios/chief-of-staff/ToolingManifest.json Dev-mode MCP server registration for Teams/Mail/Calendar tools.
scenarios/chief-of-staff/compare_grants.ps1 Diagnostic script to compare auth grants between agent instance SPs.
scenarios/chief-of-staff/scripts/bootstrap-graph-app.ps1 One-shot provisioning for the standalone Graph worker app + consent.
scenarios/chief-of-staff/src/index.ts Express host wiring (/api/health, /api/messages) + dotenv + global error hooks.
scenarios/chief-of-staff/src/agent.ts AgentApplication event routing (messages, invokes, notifications) + fast-ack for card submits.
scenarios/chief-of-staff/src/client.ts OpenAI Agents client setup, tool wiring, MCP tool registration, observability integration.
scenarios/chief-of-staff/src/openai-config.ts Azure OpenAI client configuration + @openai/agents defaults.
scenarios/chief-of-staff/src/startup-check.ts Startup banner validating critical env/config and runtime mode.
scenarios/chief-of-staff/src/scheduler.ts Cron + poller orchestration, proactive TurnContext recreation, follow-up escalation sweep.
scenarios/chief-of-staff/src/util/logger.ts Central log utility with level gating and basic redaction/truncation.
scenarios/chief-of-staff/src/util/httpLogger.ts Optional axios interceptor-based HTTP tracing with header redaction.
scenarios/chief-of-staff/src/state/persistentMap.ts File-backed persistent map with debounced atomic writes + shutdown flush.
scenarios/chief-of-staff/src/state/pendingCaptureStore.ts Persistent in-flight capture tracking + retention pruning + retry cadence helpers.
scenarios/chief-of-staff/src/state/followupStore.ts Persistent follow-up tracking + retention window for cooldown behavior.
scenarios/chief-of-staff/src/state/conversationRefs.ts Persistent per-user ConversationReference cache for proactive card DMs.
scenarios/chief-of-staff/src/graph/graphAppToken.ts App-only Graph token acquisition via MSAL confidential client.
scenarios/chief-of-staff/src/graph/plannerConfig.ts Auto-resolve Planner plan/bucket from team identifier + negative caching.
scenarios/chief-of-staff/src/graph/plannerPoller.ts Poller that detects task completion transitions with persisted baseline.
scenarios/chief-of-staff/src/graph/plannerTools.ts Planner Graph-backed tool surface for the LLM/runtime.
scenarios/chief-of-staff/src/graph/peopleTools.ts Directory/people utilities + tools (attendees list, user search, team membership gate).
scenarios/chief-of-staff/src/graph/meetingWatcher.ts Calendar-driven meeting discovery to seed capture retries.
scenarios/chief-of-staff/src/graph/meetingArtifactsFetch.ts Transcript + Copilot insights fetchers (v1.0 + beta fallback) and transcript content fetch.
scenarios/chief-of-staff/src/graph/transcriptPoller.ts Orchestrator to advance per-meeting capture state to READY and emit payloads.
scenarios/chief-of-staff/src/cos/capture.ts Capture flow prompt assembly + Planner task creation orchestration via tools.
scenarios/chief-of-staff/src/cos/brief.ts Deterministic daily/weekly brief generation using Graph + proactive Adaptive Cards.
scenarios/chief-of-staff/src/cos/followup.ts Deterministic follow-up selection + cooldown logic + proactive check-in cards.
scenarios/chief-of-staff/src/cos/taskComplete.ts Task completion handler that DMs assignees/leader and clears follow-up state.
scenarios/chief-of-staff/src/cos/escalate.ts Escalation “scan + propose options” flow implemented as an LLM-driven routine.
scenarios/chief-of-staff/src/cards/proactiveSend.ts Bot Framework proactive card sender using cached ConversationReferences.
scenarios/chief-of-staff/src/cards/briefTool.ts send_brief_card tool + Adaptive Card rendering (structured + legacy-string paths).
scenarios/chief-of-staff/src/cards/followupCards.ts Adaptive Card builders and direct-send helpers for follow-up/escalation/task assignment.
scenarios/chief-of-staff/src/cards/actionRouter.ts Adaptive Card action routing / keyword fallback handling.
Comments suppressed due to low confidence (1)

scenarios/chief-of-staff/ToolingManifest.json:31

  • ToolingManifest.json has trailing blank lines after the closing brace. This tends to cause noisy diffs and can violate repo formatting checks in some pipelines.
}






Comment thread scenarios/chief-of-staff/src/scheduler.ts
Comment thread scenarios/chief-of-staff/src/graph/meetingWatcher.ts
Comment thread scenarios/chief-of-staff/src/graph/meetingWatcher.ts
Comment thread scenarios/chief-of-staff/src/graph/meetingWatcher.ts
Comment thread scenarios/chief-of-staff/src/cos/taskComplete.ts
Comment thread scenarios/chief-of-staff/src/cos/escalate.ts
Comment thread scenarios/chief-of-staff/src/cos/escalate.ts
@Yogeshp-MSFT Yogeshp-MSFT (Yogeshp-MSFT) changed the title Add chief-of-staff scenario: Chief of Staff Teammate for Node.js Add chief-of-staff scenario: Chief of Staff autopilot using Node.js SDK Jul 23, 2026
Copilot AI review requested due to automatic review settings July 23, 2026 10:17
- meetingWatcher.ts: force calendarView to return UTC times via
  Prefer: outlook.timezone="UTC" header. Without it, Graph returns
  the mailbox owner's default TZ and the naive Date.parse(...+ 'Z')
  parsing silently computes wrong endMs / giveUpAfter timestamps.
- meetingWatcher.ts: remove the no-op
  organizerAad: ... ? undefined : undefined
  line inside qualifying.push (dead code, always evaluated to undefined).
- cos/taskComplete.ts: fix stale header comment — this handler is now
  triggered by the in-process scheduler + plannerPoller, not by a
  Power Automate email.
- cos/escalate.ts: same header-comment fix — triggered by CRON_ESCALATE
  in scheduler.ts, not by Power Automate email.
- README §1: clarify that runEscalate is also LLM-driven (draft re-plan
  proposals for the leader), alongside capture extraction + recall.

Deferred (would change functionality — kept intentionally):
- scheduler.ts::cacheConversationReference — first-inbound-user caching
  is by design; scoping to LEADER_UPN only would break proactive sends
  triggered from owner conversations.
- meetingWatcher.ts organizer-scope — leader-as-attendee (not organizer)
  is intentional so delegates/EAs can schedule meetings for the leader
  and still get captured (existing code comment documents this).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (2)

scenarios/chief-of-staff/src/graph/meetingWatcher.ts:10

  • The header comment says qualifying meetings are “organized by the leader” and “already ended”, but the implementation qualifies any meeting where both the leader and CoS are on the invite (regardless of organizer) and explicitly allows in-progress/upcoming meetings. This is also a mismatch with the PR description (“leader-organised meeting”). Either tighten the filter to organizer==leader and ended-only, or update the docs/README/PR description to match the intended behavior.
// env), keep only events that are:
//   - Teams online meetings (isOnlineMeeting = true)
//   - organized by the leader (organizer.emailAddress.address == LEADER_UPN)
//   - CoS agent is an invited attendee
//   - already ended

scenarios/chief-of-staff/src/graph/meetingWatcher.ts:73

  • This comment says the calendarView results are filtered to “organizer == leader AND CoS is invited”, but the code below actually checks only that both leader and CoS are participants (organizer OR attendee). Please update the comment so future changes don’t accidentally reintroduce the organizer-only constraint (or update the code if organizer-only is intended).
    // calendarView is the correct endpoint for expanded recurring events.
    // We read the CALENDAR of graphOwnerUpn (leader or cos-agent, per env)
    // and FILTER events where organizer == leader AND CoS is invited.

Comment thread scenarios/chief-of-staff/src/util/logger.ts Outdated
Comment thread scenarios/chief-of-staff/src/graph/transcriptPoller.ts Outdated
Comment thread scenarios/chief-of-staff/src/scheduler.ts
Comment thread scenarios/chief-of-staff/src/agent.ts
- logger.ts::safe() — replace `if (!meta)` with a nullish check so
  legitimate diagnostic values (0, false, '') are no longer silently
  dropped from logs.
- scheduler.ts::runOnCachedRef — validate botAppId before calling
  `adapter.continueConversation`. Without the check an unset
  `agent_id`/`clientId` would surface as a cryptic MSAL/OBO error
  deep inside the SDK; now we log a clear `skipped` line and return.
- agent.ts::handleUserMessage — same botAppId validation on the
  fast-ack card-submit path. If botAppId is missing we now fall back
  to the synchronous path (with a specific error message) instead of
  losing the card submit into a background failure.

Deferred (would change functional/state-machine behavior — user asked
for safe fixes only):
- transcriptPoller.ts::advanceCapture — Copilot correctly flagged that
  `markCaptureComplete` fires before the scheduler runs runCapture(),
  so a transient failure permanently dedupes the capture. Fixing this
  cleanly requires moving the mark-complete transition into the
  scheduler (across a module boundary) and reasoning about attempt
  counters + retry semantics. Left for a follow-up PR.
Copilot AI review requested due to automatic review settings July 23, 2026 10:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (2)

scenarios/chief-of-staff/src/scheduler.ts:228

  • The Planner completion poller interval can overlap with itself if fireInAuthedContext/pollForCompletedTasks/runTaskComplete takes longer than POLL_TASKS_MS, because setInterval doesn't await. That overlap can cause duplicate detections or extra Graph load. The meeting poll has an in-flight guard; Planner poll should too.
  // ── Poll: completed Planner tasks → runTaskComplete ──
  intervalIds.push(
    setInterval(async () => {
      await fireInAuthedContext(deps, 'planner-poll', async (ctx, state, client) => {
        const done = await pollForCompletedTasks({

scenarios/chief-of-staff/src/graph/meetingWatcher.ts:10

  • Header comment says meetings must be "organized by the leader" and "already ended", but the implementation qualifies meetings when both leader+CoS are on the invite and can include in-progress/upcoming meetings. This mismatch will confuse maintainers and reviewers.
// env), keep only events that are:
//   - Teams online meetings (isOnlineMeeting = true)
//   - organized by the leader (organizer.emailAddress.address == LEADER_UPN)
//   - CoS agent is an invited attendee
//   - already ended

Comment thread scenarios/chief-of-staff/src/graph/graphAppToken.ts
Comment thread scenarios/chief-of-staff/src/scheduler.ts
Comment thread scenarios/chief-of-staff/src/startup-check.ts
Comment thread scenarios/chief-of-staff/package.json
Comment thread scenarios/chief-of-staff/.env.template
keshav Keshari (keshavk-msft) added a commit to keshavk-msft/Agent365-Samples that referenced this pull request Jul 24, 2026
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.
…e strips fields

pendingCaptureStore.markCaptureComplete() sets transcriptContent,
insightsActionItems and insightsMeetingNotes to undefined for memory
savings. Because store.get(eventId) returns the same object reference
as the local `cap` variable, that mutation happens in place - so
building the return object AFTER mark-complete lands undefined values.
Capture consumers then see an empty transcript and the LLM hallucinates
a fetch error.

Reorder the READY branch: build `result` from `cap` first, then
call markCaptureComplete(cap.eventId), then `return result`. Adds a
short comment above the snapshot explaining the aliasing so this does
not regress.

No behavioural change on the retry / give-up branches; only affects the
success path in advanceCapture().
Copilot AI review requested due to automatic review settings July 29, 2026 07:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (5)

scenarios/chief-of-staff/src/scheduler.ts:247

  • Unlike the meeting poller, the Planner completion poller doesn’t guard against overlapping ticks. If Graph is slow and a tick runs longer than POLL_TASKS_MS, concurrent polls can double-detect the same transition and fire runTaskComplete twice (duplicate DMs). Add an in-flight guard similar to meetingPollInFlight.
  // ── Poll: completed Planner tasks → runTaskComplete ──
  intervalIds.push(
    setInterval(async () => {
      await fireInAuthedContext(deps, 'planner-poll', async (ctx, state, client) => {

scenarios/chief-of-staff/src/startup-check.ts:202

  • The startup banner says missing GRAPH_APP_* will fall back to agentic OBO, but several flows/tools in this sample call acquireAppOnlyGraphToken() and will throw if the standalone worker env vars aren’t set. The banner should reflect that these flows require the worker (or explicitly document which calls actually fall back).
    scenarios/chief-of-staff/compare_grants.ps1:1
  • This PowerShell script is missing the standard Microsoft copyright header used by other repo .ps1 scripts (e.g., scripts/e2e/Start-Agent.ps1). Please add it at the top of the file.
# ──────────────────────────────────────────────────────────────────────────
#  Compare delegated permission grants and app-role assignments between two
#  Agent 365 agent instance service principals — useful when diagnosing
#  "why does agent A see tool X but agent B doesn't?".

scenarios/chief-of-staff/scripts/bootstrap-graph-app.ps1:1

  • This PowerShell script is missing the standard Microsoft copyright header used by other repo .ps1 scripts (e.g., scripts/e2e/Start-Agent.ps1). Please add it at the top of the file.
# bootstrap-graph-app.ps1
#
# One-shot provisioning of the standalone Graph worker app for cos-agent.

scenarios/chief-of-staff/DESIGN.md:59

  • DESIGN.md says state is intentionally in-memory (Map only), but the implementation in this sample uses file-backed persistence (PersistentMap, STATE_DIR) for restart safety. Update the design principles text so it matches the actual behavior and guidance.
5. **State is intentionally in-memory for the MVP.** All three stores
   (`followupStore`, `pendingCaptureStore`, `conversationRefs`) are
   `Map` instances. Fine for a demo; swap for persistent storage before
   production (see §13).

…lates

Every turn now refreshes the observability token cache for BOTH the blueprint and agentic-instance identities. The exporter partitions span groups by (agentId, tenantId); groups without a cached token are silently skipped, which left the M365 admin center Activity tab empty even though batch-level agent365-export succeeded events fired. Also installs the ObservabilityHostingManager middleware on the shared CloudAdapter so BaggageMiddleware + OutputLoggingMiddleware run per turn, and adds a botAppId null-check in the handleUserMessage card-submit async continuation. DESIGN.md 11.2 expanded to describe the three-layer wiring (client.ts tracer, index.ts middleware install, agent.ts per-turn refresh) plus the silent-skip gotcha and log-evidence markers for verification.
Copilot AI review requested due to automatic review settings August 3, 2026 09:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated no new comments.

Suppressed comments (6)

scenarios/chief-of-staff/.env.template:81

  • This template claims GRAPH_APP_* can be left blank to fall back to agentic OBO, but the current implementation uses the standalone Graph worker for Planner/Graph calls and will fail without it. The template should match the actual requirement.
# ─── Standalone Graph worker app (recommended) ───
# When all three are set, EVERY Graph API call uses this app's client
# credentials (application permissions) instead of the agentic OBO chain.
# Simpler consent, cleaner reproduction. See README §2b.
# Leave blank to fall back to the agentic OBO exchange.

scenarios/chief-of-staff/src/graph/meetingWatcher.ts:47

  • MeetingWatcherOptions comments say leaderUpn must be the organizer and cosAgentUpn must be an attendee, but discoverQualifyingMeetings currently checks for either being organizer OR attendee. Update these field docs to avoid misconfiguring callers.
  /** UPN who must be the event organizer for it to qualify. */
  leaderUpn: string;
  /** UPN who must be in event attendees for it to qualify (the CoS agent). */
  cosAgentUpn: string;

scenarios/chief-of-staff/src/startup-check.ts:203

  • The startup banner says missing GRAPH_APP_* will fall back to agentic OBO, but several Graph paths in this sample (e.g., Planner tools) unconditionally use the standalone Graph worker. This message is misleading and can cause confusing runtime failures when scheduler/pollers start.
    scenarios/chief-of-staff/README.md:35
  • README says Capture triggers only for leader-organised meetings, but the current meeting watcher qualifies any meeting where both the leader and CoS are on the invite (even if a delegate organized it). Please update this line (or change the implementation) so docs and behavior match.
| **Capture** — extracts action items + decisions from Teams meetings and creates Planner tasks | Calendar poller finds a leader-organised meeting the CoS was invited to, transcript is fetched, LLM extracts |

scenarios/chief-of-staff/src/graph/plannerPoller.ts:52

  • The function comment says the first poll after boot only seeds the baseline, but when a baseline is hydrated from disk (firstPollDone=true) the first poll will emit transitions. Update the doc comment to reflect the actual behavior.
/**
 * Poll Planner for tasks that just became 100% complete. Empty on failure.
 * First poll after boot only seeds the baseline — no completions reported
 * (otherwise every already-done task would fire on first tick).
 */

scenarios/chief-of-staff/src/graph/meetingWatcher.ts:10

  • The header comment describes filtering to leader-organized and already-ended meetings, but the implementation qualifies meetings as long as both leader+CoS are on the invite and allows upcoming/in-progress meetings. Please align the comment (and associated docs) with the actual behavior.
// env), keep only events that are:
//   - Teams online meetings (isOnlineMeeting = true)
//   - organized by the leader (organizer.emailAddress.address == LEADER_UPN)
//   - CoS agent is an invited attendee
//   - already ended

…AC Activity

Extract A365 observability wiring into src/observability.ts as a single source of truth for identity resolution, token cache warming, and OTEL baggage. Runtime agent identity now comes from activity.recipient.agenticAppId (the agentic instance) rather than the blueprint id; blueprint kept as span metadata only. Adds runWithObservabilityContext() so proactive / scheduled turns (card-submit continuation, invoke continuation, scheduler crons) carry the same tenant + agent + caller baggage that inbound /api/messages turns get from the hosting middleware. buildUserDetails is now async and falls back to a Graph /users/{aad} lookup (cached via graph/peopleTools.ts) so user.email carries the real UPN on Teams-channel activities, which don't populate from.agenticUserId. New ClientOptions.humanInitiated flag lets scheduler-created clients suppress caller attribution so an autonomous cron doesn't report the last inbound user. .env.template adds agent365Observability__agentInstanceId with clear comments distinguishing runtime vs blueprint id. DESIGN.md 11.2 fully rewritten to describe the four-layer wiring plus the three gotchas (blueprint-vs-instance, Teams-no-UPN, proactive-bypasses-baggage) and the tenant policy that hashes UPNs even when we emit them correctly. README troubleshooting adds two rows for empty MAC Activity tab and hashed UPN column.
Copilot AI review requested due to automatic review settings August 5, 2026 06:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 39 out of 39 changed files in this pull request and generated no new comments.

Suppressed comments (8)

scenarios/chief-of-staff/.env.template:84

  • .env.template says leaving GRAPH_APP_* blank will fall back to the agentic OBO exchange for "EVERY Graph API call", but many code paths (Planner/Brief/Follow-up, plannerTools helpers, plannerConfig resolution) call acquireAppOnlyGraphToken() directly and will fail if the worker app isn't configured. Either implement a real fallback everywhere or update the template to set the expectation correctly.
# ─── Standalone Graph worker app (recommended) ───
# When all three are set, EVERY Graph API call uses this app's client
# credentials (application permissions) instead of the agentic OBO chain.
# Simpler consent, cleaner reproduction. See README §2b.
# Leave blank to fall back to the agentic OBO exchange.
GRAPH_APP_ID=
GRAPH_APP_SECRET=
GRAPH_TENANT_ID=

scenarios/chief-of-staff/src/cards/actionRouter.ts:123

  • proposeMeetingSlots() claims to propose times in DISPLAY_TZ, but the ISO timestamps are only correct for Asia/Kolkata. For any other IANA zone (including the BRIEF_DISPLAY_TZ examples in .env.template), this schedules meetings at the wrong wall-clock time because it treats the requested local hour as UTC.
    for (const hour of HOURS) {
      if (slots.length >= 3) break;
      // IST is UTC+5:30 → subtract 330 min to get equivalent UTC instant.
      // For non-IST TZs, use the local time as UTC (acceptable demo drift).
      const offsetMin = DISPLAY_TZ === 'Asia/Kolkata' ? 330 : 0;

scenarios/chief-of-staff/src/cards/actionRouter.ts:629

  • The owner DM in book_meeting hard-codes the leader name as "Alex", which will be incorrect for other deployments and can confuse recipients.
      const ownerMsg =
        `📅 **Meeting scheduled to unblock: ${taskTitle}**\n\n` +
        `Time: ${timeslot}\n` +
        `The leader (Alex) picked this slot. Calendar invite coming next.` +
        (timeslotIso ? `\n\n_ISO start: ${timeslotIso}_` : '');

scenarios/chief-of-staff/src/graph/meetingWatcher.ts:10

  • The file header comment says meetings are filtered to "organized by the leader" and "already ended", but the implementation qualifies meetings whenever both leader+CoS are on the invite (regardless of organizer) and allows in-progress/upcoming meetings (retry loop handles readiness). This mismatch makes it harder to understand and maintain the capture logic.
// env), keep only events that are:
//   - Teams online meetings (isOnlineMeeting = true)
//   - organized by the leader (organizer.emailAddress.address == LEADER_UPN)
//   - CoS agent is an invited attendee
//   - already ended

scenarios/chief-of-staff/src/graph/plannerPoller.ts:52

  • The pollForCompletedTasks() docstring says the first poll "only seeds the baseline" and reports no completions, but the code explicitly treats a hydrated baseline (lastProgress.size > 0) as a normal comparison where completions during downtime will fire on the next poll. The comment should reflect that conditional behavior.
/**
 * Poll Planner for tasks that just became 100% complete. Empty on failure.
 * First poll after boot only seeds the baseline — no completions reported
 * (otherwise every already-done task would fire on first tick).
 */

scenarios/chief-of-staff/src/index.ts:65

  • Keeping the process alive after an uncaughtException is unsafe in Node.js because the runtime may be in an undefined state (potentially corrupting in-memory stores and scheduled work). Prefer logging and exiting so the process supervisor can restart cleanly.
process.on('uncaughtException', (err) => {
  console.error('[process] uncaughtException — keeping process alive.', {
    message: err?.message,
    stack: err?.stack,
  });
});

scenarios/chief-of-staff/README.md:35

  • The README describes Capture as triggering only for "leader-organised" meetings, but meetingWatcher.ts qualifies meetings when both leader+CoS are on the invite (regardless of organizer). Either the code or the docs should be updated so setup/expectations match actual behavior.
| Capability | Trigger |
|---|---|
| **Capture** — extracts action items + decisions from Teams meetings and creates Planner tasks | Calendar poller finds a leader-organised meeting the CoS was invited to, transcript is fetched, LLM extracts |
| **Daily Brief** — Adaptive Card DM to the leader with priorities, watch items, upcoming meetings | Cron (default 8 AM weekdays, gated behind `BRIEF_ENABLED`) |

scenarios/chief-of-staff/DESIGN.md:55

  • Design principle #5 claims state is "intentionally in-memory" and stores are Map instances, but the implementation uses PersistentMap with a file-backed STATE_DIR and hydration/flush logic. This inconsistency can mislead readers about restart behavior and deployment requirements.
4. **One path to Graph.** Every Graph call uses the standalone
   cos-graph-worker application-permission token. No delegated /
   agentic-OBO Graph path exists — blueprint and agent-instance
   identities get no extra Graph scopes. See §7.
5. **State is intentionally in-memory for the MVP.** All three stores
   (`followupStore`, `pendingCaptureStore`, `conversationRefs`) are
   `Map` instances. Fine for a demo; swap for persistent storage before
   production (see §13).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants