outlook-cli

Plan: Copilot in Excel, PowerPoint and Word through a browser extension

Status: experimental implementation added (2026-10-22). Live tab discovery passed on 2026-10-23. Excel read-only and single-cell edit checks passed. PowerPoint read-only, title-edit and slide-insertion checks passed after manual pane opening. Saved files were independently checked. Unit and local pipe/socket integration tests pass. Do not treat the spike results below as release verification.

Implementation status

Release checklist (new implementation)

Live checks (2026-10-23): after readiness and exact-prompt correlation changes, Excel returned the correct sample-table description. A follow-up set only Sheet1!E1 to Bridge live check 2026-10-23. The saved workbook was downloaded and checked: the marker is present, all 12 original cells are unchanged, and xl/styles.xml is unchanged. Graph reported a newer modification time before downloads contained the edit, so timestamp evidence is not sufficient. A later read was refused because the prompt box contained a draft; it was not cleared. Evidence files are in /tmp/copilot-live-checks/.

PowerPoint initially opened the disposable Presentation 4.pptx, but its Copilot pane did not become ready within 30 seconds. After Ross opened the pane manually, the CLI read the two slide titles, changed only slide 1’s title to Copilot bridge live check, and appended slide 3, Bridge verification, with Read/Edit/Saved bullets in the same conversation. The saved title edit matched the original slide XML with only its title replaced; slide 2 was byte-identical. Office also refreshed date fields in the master/layouts from 2026-10-22 to 2026-10-23. After insertion, both existing slide XML files were byte-identical to their prior versions. Slide 3 uses the existing slideLayout2.xml. All three final slides were rendered through LibreOffice and visually checked. No Office lock files were present. Evidence is in /tmp/copilot-live-checks/ppt-*.

Extension 0.2.0 diagnostics confirmed the closed PowerPoint pane was mounted but lacked a visible prompt. A subsequent CLI request opened it automatically and returned slide 1’s correct title, with no action events. Word’s dummy document has a ready prompt and an Allow editing menu; its protocol differs from Excel. No Word prompt was sent by the CLI.

Word checks (2026-10-23, extension 0.3.2, controller v5): with the drawer open, the CLI sent a read-only prompt to dummy_document.docx. Copilot returned a one-sentence summary and called only get_active_document_html. With the drawer closed, the bridge opened the drawer and the same prompt completed. A third prompt added one paragraph at the end. The actions were get_active_document_html, begin_edit_session, run_js_code and end_edit_session. No approval came up. The saved .docx was downloaded and compared with a copy from before the edit. The new last paragraph is Bridge live check 2026-10-23. (Copilot added the full stop). All 17 original paragraphs keep their text and order. Word Online wrote the bold run of the previous last paragraph again as <w:b w:val="1"/><w:bCs w:val="1"/>, with the same meaning. Only word/settings.xml (one new rsid) and docProps/core.xml changed in the other parts. Evidence is in /tmp/copilot-live-checks/word-v5-* and /tmp/copilot-live-checks/word-edit/.

Chrome reload initially raced the previous native host’s socket lock. A bounded lock wait and regression test now cover that case.

Local checks: .venv/bin/python -m pytest -q (315 passed, 1 skipped) and node --test extension/tests/*.test.js (32 passed). The JS fixtures are synthetic and contain no real document or tenant data.

Context

outlook-cli copilot ask uses the main Microsoft 365 Copilot chat. That chat can read files, but it cannot edit an open document. Its document tools only make a new copy.

The Copilot agent inside each Office app can edit the document. Ross wants the CLI and the MCP server to send a prompt to that agent for a given file and app, and to get the answer back.

The in-app agents need the real Office web editor, because:

So the CLI controls an Office tab in Ross’s own Chrome, through an extension and a native messaging host. suimi8/augloop-copilot-proxy connects to AugLoop without an editor. It reads tokens from the memory of the Office process, fakes the client and cannot edit. Do not use that method.

Spike results (2026-10-22)

A spike extension in the scratchpad controlled real tabs for Book 1.xlsx and Presentation 4.pptx.

  Excel PowerPoint
Open the pane Button aria-label="Chat with Copilot" in the editor frame Same
Editor frame *.officeapps.live.com/x/_layouts/xlviewerinternal.aspx *.officeapps.live.com/pods/ppt.aspx
Pane frame *.resources.office.net/.../taskpane.html (“Excel Copilot Chat Add-in”) Same host (“Copilot Chat”)
Prompt box contenteditable, aria-label="Chat input" contenteditable, aria-label="Type your message"
Type the prompt document.execCommand("insertText") Same
Send Button aria-label="Send". It appears after the box has text. Same
AugLoop endpoint wss://augloop.office.com wss://msit.augloop.svc.cloud.microsoft
Agent protocol AugLoop_OfficeAgent_* AugLoop_PptAgentExperimental_*
Answer text OfficeAgentAssistantMessage.text (pieces, then the full text) PptAgentExperimentalOutputAnnotation (chunkContent, then finalResponse.assistantResponse)
End of turn status: "completed" responseStatus: "complete"
Edit OfficeAgentInvokeFunctionAnnotation, namespace: "excel", functionName: "executeScript" CopilotActionResponseObject from the client
Saved in OneDrive Yes, some minutes after the edit Yes
Follow-up prompt Not tested Works in the same pane

Lessons:

  1. Never change the editor DOM. The spike wrote a data-* attribute on PowerPoint’s editable slide nodes, and PowerPoint failed with “Sorry, we ran into a problem”. With ids kept in a JavaScript map, the error did not come back.
  2. A reloaded extension loses its scripts in open tabs. The service worker must inject its scripts again into open Office tabs when it starts.
  3. The edit shows at once, but OneDrive gets it later. For Excel, it took some minutes.
  4. A main-world WebSocket hook sees the AugLoop traffic in the editor frame, in both apps.

Goals

Non-goals

Incident 2026-11-06 (extension 0.3.2)

Target: a shared SharePoint deck. The prompt had several paragraphs and a bulleted list. It asked Copilot to edit slide 6 only.

  1. --open --new-chat, tab not open: the tab opened, then “The Copilot pane did not open in time.” The bridge clicked the Copilot button as soon as it appeared. The editor was probably not ready to handle the click.
  2. --new-chat, pane open from run 1: “The pane has no New chat button.” A fresh pane has no New chat button. The button appears only after a conversation starts.
  3. No flags: Copilot edited only slide 6, and the file was saved. The command did not return. Ross stopped it after about six minutes (--timeout 400).

Cause of run 3: the hook bound a request only when the signal query was exactly the typed prompt. The pane is a rich text editor, so typed newlines, list markers and spaces come back changed. The hook never bound, the adapters got no records, and the worker had only its timer. The 0.3.2 timer started after the pane opened. After the interrupt, the cancel marked the tab uncertain, and status showed that as busy: True.

Signal count: inspect showed two PptAgentExperimentalSignal sends. These counts cover the life of the page, not one request. Runs 1 and 2 stopped before arm and before send_prompt, which is the only call that types. So the bridge did not send twice. The second signal is either a pane start signal or a task operation (the signal has allowResume and taskOperationParams). Version 0.4.0 reports per-request signal classes to settle this in the next live run.

Observed PowerPoint schemas (names and keys only): PptAgentExperimentalOutputAnnotation (finalResponse, responseStatus, queryId, taskId, sydneyConversationId), PptAgentExperimentalOutputAnnotationV1 (pptEditsPayload, responseStatus, queryId), Core_TaskInformationAnnotation (taskStatus, ownerId). Five V1 annotations matched five CopilotActionResponseObject sends, so V1 carries the edit batches.

Architecture

outlook-cli / MCP ──Unix socket──> native host (Python) ──stdio──> extension service worker
                                   started by Chrome                     │
                                                          content scripts in the Office tab
                                                          ├─ hook.js (main world, editor frame):
                                                          │    reads AugLoop frames, app adapter
                                                          │    turns them into events
                                                          └─ controller.js (isolated world):
                                                               opens the pane, types, sends

1. Extension (extension/)

Manifest V3, loaded unpacked. A key in the manifest gives a fixed extension ID.

2. Native host (outlook_draft/copilot_bridge/)

3. CLI (outlook_draft/commands/copilot.py)

outlook-cli copilot app ask "<prompt>" --file <url|id|path> [--site S] [--library L]
                         [--app excel|powerpoint|word] [--new-chat] [--open]
                         [--wait-saved] [--timeout 300] [--json]
outlook-cli copilot app status            # host, extension, open Office tabs
outlook-cli copilot extension install     # native host manifest and launcher
outlook-cli copilot native-host           # started by Chrome only (hidden)

4. MCP tool (outlook_draft/mcp/copilot.py)

copilot_app(prompt, file, app=None, site=None, library=None, new_chat=False, open_tab=False, wait_saved=False):

Safety rules

  1. Never approve an agent action, a permission request or a plan proposal. Stop, and return approval_required.
  2. Never click Done, Undo, Keep or the Allow editing control. Report the Allow editing setting in status.
  3. Never change the editor DOM.
  4. Never send document content, tokens or raw AugLoop frames out of the page. Send only adapter events.
  5. The socket accepts only the same user, and the extension accepts messages only from its native host.
  6. One request runs in a tab at a time. A second request gets the error “busy”.
  7. If the file is not in Ross’s own OneDrive, the CLI prints a caution, because other people see the edits at once.

Implementation phases

  1. Bridge: extension skeleton, native host, socket, extension install, app status. Done when status lists the open Office tabs and their apps.
  2. Excel: controller, Excel adapter, app ask with streaming. Done when the Book 1 test passes from the CLI, and the result reports the executeScript action.
  3. PowerPoint: adapter. Done when the title test and the agenda slide test pass, and no error dialog appears.
  4. Approvals: set Allow editing to ask in each app, record the approval messages, and add approval_required to the adapters. Done when the CLI stops and reports the request, and the document does not change.
  5. MCP and docs: the copilot_app tool, README, AGENTS.md, docs/copilot-tools.md.
  6. Word: spike the pane and the protocol, then add an adapter.
  7. Additions: --open, --new-chat, --wait-saved, and behaviour in background tabs.

Tests

Open questions

  1. What does an approval request look like when Allow editing asks first? (Phase 4)
  2. Does the agent work when its tab is in the background, or does Chrome slow its timers too much? Do we need to bring the tab to the front?
  3. Does Word use OfficeAgent like Excel, or its own agent?
  4. Should the tool refuse files that are not Ross’s by default, and need a flag such as --allow-shared?
  5. The pane keeps its own conversation. Is that enough, or do we need --new-chat for each call by default?
  6. Do we need Edge support? The native host manifest folder and the extension ID are different there.

Risks

Clean-up of the spike