copilot-operator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +556 -0
- package/dist/src/api/app.module.js +18 -0
- package/dist/src/api/dataAcl.js +109 -0
- package/dist/src/api/folderPicker.js +110 -0
- package/dist/src/api/main.js +132 -0
- package/dist/src/api/operator.controller.js +975 -0
- package/dist/src/api/operator.service.js +1971 -0
- package/dist/src/api/saveToDesktop.js +157 -0
- package/dist/src/api/security.js +225 -0
- package/dist/src/api/settings.js +57 -0
- package/dist/src/cli.js +531 -0
- package/dist/src/config/layout.js +66 -0
- package/dist/src/config/lockedPolicy.js +176 -0
- package/dist/src/config/schema.js +487 -0
- package/dist/src/context/contextFiles.js +295 -0
- package/dist/src/context/desktopMirror.js +113 -0
- package/dist/src/context/projectMirror.js +364 -0
- package/dist/src/exec/authorizer.js +61 -0
- package/dist/src/exec/checks.js +285 -0
- package/dist/src/exec/confinement.js +224 -0
- package/dist/src/exec/dangerous.js +161 -0
- package/dist/src/exec/environment.js +80 -0
- package/dist/src/exec/isolation.js +166 -0
- package/dist/src/exec/network.js +242 -0
- package/dist/src/exec/policy.js +201 -0
- package/dist/src/exec/policyManifest.js +84 -0
- package/dist/src/exec/processes.js +329 -0
- package/dist/src/exec/programs.js +290 -0
- package/dist/src/exec/redaction.js +93 -0
- package/dist/src/exec/reportFile.js +154 -0
- package/dist/src/exec/runner.js +245 -0
- package/dist/src/exec/scriptFiles.js +81 -0
- package/dist/src/exec/shellExecuteTrap.js +221 -0
- package/dist/src/exec/shells.js +298 -0
- package/dist/src/exec/stepEnv.js +72 -0
- package/dist/src/exec/winps.js +17 -0
- package/dist/src/exec/workDir.js +94 -0
- package/dist/src/log/runLog.js +43 -0
- package/dist/src/orchestrator/derivedChecks.js +95 -0
- package/dist/src/orchestrator/review.js +590 -0
- package/dist/src/orchestrator/taskRunner.js +1582 -0
- package/dist/src/plan/brief.js +1785 -0
- package/dist/src/plan/importPlan.js +202 -0
- package/dist/src/plan/schema.js +483 -0
- package/dist/src/plan/script.js +1012 -0
- package/dist/src/plan/systemGuide.js +1851 -0
- package/dist/src/protocol/parser.js +224 -0
- package/dist/src/protocol/replySchema.js +207 -0
- package/dist/src/protocol/reporter.js +75 -0
- package/dist/src/protocol/reviewSchema.js +205 -0
- package/dist/src/session/compose.js +67 -0
- package/dist/src/session/debugExport.js +165 -0
- package/dist/src/session/events.js +34 -0
- package/dist/src/session/exportRecord.js +261 -0
- package/dist/src/session/exports.js +484 -0
- package/dist/src/session/model.js +22 -0
- package/dist/src/session/retention.js +34 -0
- package/dist/src/session/runName.js +36 -0
- package/dist/src/session/store.js +440 -0
- package/dist/src/session/story.js +198 -0
- package/dist/src/transport/acceptance.js +34 -0
- package/dist/src/transport/chatSession.js +73 -0
- package/dist/src/transport/copilotTransport.js +1208 -0
- package/dist/src/transport/edgeCrash.js +59 -0
- package/dist/src/transport/locators.js +261 -0
- package/dist/src/transport/profileLock.js +102 -0
- package/dist/src/util/pacing.js +89 -0
- package/dist/src/vcs/commitHygiene.js +102 -0
- package/dist/src/vcs/git.js +286 -0
- package/dist/src/vcs/taskVcs.js +343 -0
- package/dist/web/404.html +15 -0
- package/dist/web/_next/static/chunks/12.f1f4d35b52633e8d.js +1 -0
- package/dist/web/_next/static/chunks/131-bae98f790a541679.js +1 -0
- package/dist/web/_next/static/chunks/197-f7c33a0114fc3db3.js +1 -0
- package/dist/web/_next/static/chunks/489.158bbe303a595fba.js +1 -0
- package/dist/web/_next/static/chunks/643-34b9f66a6c317c4a.js +1 -0
- package/dist/web/_next/static/chunks/867-03b7cac8f79896c3.js +1 -0
- package/dist/web/_next/static/chunks/955-63de06e182d98949.js +1 -0
- package/dist/web/_next/static/chunks/app/_not-found/page-0d0d8e7e62efca12.js +1 -0
- package/dist/web/_next/static/chunks/app/appearance/page-501842e7aeac7f6b.js +1 -0
- package/dist/web/_next/static/chunks/app/defaults/page-ed858127cbdfb4ab.js +1 -0
- package/dist/web/_next/static/chunks/app/history/page-bad4f3e89b485719.js +1 -0
- package/dist/web/_next/static/chunks/app/import/page-db1f5f1ab2f5cad1.js +1 -0
- package/dist/web/_next/static/chunks/app/layout-72251c52cc54f95e.js +1 -0
- package/dist/web/_next/static/chunks/app/level1/page-cf3e50cf64fecf4f.js +1 -0
- package/dist/web/_next/static/chunks/app/page-6fd16e5c8cf3b20f.js +1 -0
- package/dist/web/_next/static/chunks/app/presets/page-951e78b80e0e22e6.js +1 -0
- package/dist/web/_next/static/chunks/app/sessions/view/page-f2e63618097d7221.js +1 -0
- package/dist/web/_next/static/chunks/app/system/page-44e7954c0e48f4ba.js +1 -0
- package/dist/web/_next/static/chunks/c7879cf7-b5ab1053c1d9a2e7.js +1 -0
- package/dist/web/_next/static/chunks/framework-9ac6a346bd42cedd.js +1 -0
- package/dist/web/_next/static/chunks/main-6be4444e2d667e41.js +1 -0
- package/dist/web/_next/static/chunks/main-app-e3a5f713ba31557e.js +1 -0
- package/dist/web/_next/static/chunks/pages/_app-32d20d5a04949d29.js +1 -0
- package/dist/web/_next/static/chunks/pages/_error-abbab53f25ea29b0.js +1 -0
- package/dist/web/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/web/_next/static/chunks/webpack-0f31b926d9a9c5a4.js +1 -0
- package/dist/web/_next/static/css/d77735cdc9020a86.css +1 -0
- package/dist/web/_next/static/u8rZNwh4d3n-HGJnfZ72Y/_buildManifest.js +1 -0
- package/dist/web/_next/static/u8rZNwh4d3n-HGJnfZ72Y/_ssgManifest.js +1 -0
- package/dist/web/appearance.html +15 -0
- package/dist/web/appearance.txt +26 -0
- package/dist/web/defaults.html +15 -0
- package/dist/web/defaults.txt +26 -0
- package/dist/web/history.html +15 -0
- package/dist/web/history.txt +26 -0
- package/dist/web/import.html +15 -0
- package/dist/web/import.txt +26 -0
- package/dist/web/index.html +15 -0
- package/dist/web/index.txt +26 -0
- package/dist/web/level1.html +15 -0
- package/dist/web/level1.txt +26 -0
- package/dist/web/presets.html +18 -0
- package/dist/web/presets.txt +26 -0
- package/dist/web/sessions/view.html +15 -0
- package/dist/web/sessions/view.txt +26 -0
- package/dist/web/system.html +15 -0
- package/dist/web/system.txt +26 -0
- package/package.json +111 -0
- package/prompts/level1.md +535 -0
- package/prompts/organisation.bg.md +44 -0
- package/prompts/organisation.en.md +44 -0
- package/prompts/persona.bg.md +17 -0
- package/prompts/persona.en.md +17 -0
- package/prompts/review1.md +277 -0
- package/prompts/work.bg.md +19 -0
- package/prompts/work.en.md +19 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ivan Markov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
# copilot-operator
|
|
2
|
+
|
|
3
|
+
A Windows bot that runs a task loop with Microsoft 365 Copilot.
|
|
4
|
+
|
|
5
|
+
Copilot decides what to do, the bot does it and reports back, and the two keep going until
|
|
6
|
+
Copilot says the task is finished. The bot drives the real Copilot web app in a real Edge
|
|
7
|
+
browser, so it needs no API licence and no admin consent.
|
|
8
|
+
|
|
9
|
+
> **Status: working, every part verified against a live Microsoft 365 Copilot tenant.**
|
|
10
|
+
> The loop, chat naming, reply parsing, command execution and the results file are confirmed.
|
|
11
|
+
> A step printing for 92 s survived while a silent one was stopped at its idle limit. Project files
|
|
12
|
+
> mirrored to the Desktop were attached and answered questions from. Unattended mode is the
|
|
13
|
+
> one thing still untried, deliberately.
|
|
14
|
+
|
|
15
|
+
## What it does
|
|
16
|
+
|
|
17
|
+
1. Opens a conversation and sends **level 1**, the base prompt about how the runner works: phases, json
|
|
18
|
+
format, stop word, the final summary, the rules nothing else can override.
|
|
19
|
+
2. Sends **level 2**, the user's instructions for the project and the team, together with
|
|
20
|
+
the **task**.
|
|
21
|
+
3. Reads Copilot's reply and parses a strict JSON block out of it. A reply that does not
|
|
22
|
+
match is sent back with the reason.
|
|
23
|
+
4. Runs the commands in PowerShell or `cmd`, in order, capturing stdout, stderr and exit codes.
|
|
24
|
+
Commands are the only thing it runs: nothing the chat attaches is fetched, saved or executed.
|
|
25
|
+
5. Writes the whole terminal output to a `.txt` file and attaches it to the chat.
|
|
26
|
+
6. Repeats until Copilot finishes with a **summary** of what it did and what the result is.
|
|
27
|
+
7. Runs the next queued task in the same conversation.
|
|
28
|
+
|
|
29
|
+
A **session** is one conversation with a queue of tasks. Selected parts of a project can be
|
|
30
|
+
mirrored to a Desktop folder and attached, so the chat can see the code it is asked about.
|
|
31
|
+
|
|
32
|
+
There is a web UI for all of this, and a terminal command for a single task.
|
|
33
|
+
|
|
34
|
+
## Safety
|
|
35
|
+
|
|
36
|
+
The bot executes commands written by a language model — commands only; it has no way to take a
|
|
37
|
+
file from the chat. Confirm mode is on by default: every step is shown and waits for a keypress.
|
|
38
|
+
Unattended mode is behind an explicit flag and its own preconditions. There is a deny list, a
|
|
39
|
+
per-step timeout and an iteration cap. Everything a step prints goes to the chat as a file, so
|
|
40
|
+
secret-shaped strings — tokens, keys, passwords in assignments and URLs, private key blocks —
|
|
41
|
+
are redacted from every report before it is uploaded, always, with `report.redactPatterns`
|
|
42
|
+
applied on top. Running it in a dedicated Windows account or Windows Sandbox is recommended.
|
|
43
|
+
|
|
44
|
+
**Where the reports go.** Every report — the terminal output of every step, the check results,
|
|
45
|
+
the reviewer's transcripts — and any files the project mirror attaches are uploaded into the
|
|
46
|
+
Copilot chat, which stores them in the signed-in account's OneDrive for Business and in the
|
|
47
|
+
Copilot conversation history. They stay inside the company's own Microsoft 365 tenant, under its
|
|
48
|
+
Purview DLP policies, sensitivity labels, retention and eDiscovery; nothing goes to any other
|
|
49
|
+
service. A company deploying this should tell its data-protection and DLP teams so, and set
|
|
50
|
+
`runsRetentionDays` so the local copies under `runs/` follow the same retention rule. The run
|
|
51
|
+
log says it at the start of every run.
|
|
52
|
+
|
|
53
|
+
### What it will not run
|
|
54
|
+
|
|
55
|
+
Some things are refused whatever the task or the configuration says, because a security team
|
|
56
|
+
watching the machine cannot tell this tool's use of them from an attacker's, and would be right
|
|
57
|
+
not to try. The list lives in `src/exec/dangerous.ts`, is not part of the configuration schema,
|
|
58
|
+
and cannot be edited away; `execution.denyPatterns` is the operator's own list and is applied on
|
|
59
|
+
top of it — it can add, never subtract.
|
|
60
|
+
|
|
61
|
+
| Refused | The ordinary way instead |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `certutil`, `bitsadmin` | read or convert files with PowerShell in a step of their own |
|
|
64
|
+
| `wscript`, `cscript`, `mshta` | run the tool itself; never `.js`, `.vbs` or `.hta` as code |
|
|
65
|
+
| `regsvr32`, `rundll32`, `installutil`, `forfiles` | call the program directly |
|
|
66
|
+
| `-EncodedCommand`, base64 decoded into code, `Invoke-Expression` | write the command out in full |
|
|
67
|
+
| fetching code and running it in one line (`iwr … \| iex`) | write the commands as steps |
|
|
68
|
+
| running anything out of `%TEMP%` | work inside the project folder |
|
|
69
|
+
| antivirus exclusions, scheduled tasks, Run keys, new services | nothing here should outlive the run |
|
|
70
|
+
| opening a file or URL through its registered handler (`Invoke-Item`, `Start-Process` on a document) | start the program by name |
|
|
71
|
+
| writing under `.git\`, or `git config <key> <value>` | the runner owns the repository; read history with git |
|
|
72
|
+
| reaching the runner itself (its ports, its data folder, its key) | nothing: the bot is not part of the task |
|
|
73
|
+
|
|
74
|
+
Every step is a command and every command is read in full before it runs, so there is nowhere for
|
|
75
|
+
any of this to arrive unread. A name hidden rather than written — a caret or empty quotes inside a
|
|
76
|
+
word, a name assembled from pieces, one resolved by wildcard — is refused as the hiding it is. A
|
|
77
|
+
script a step runs with `pwsh -File` is read from disk at the gate and held to the same rules; a
|
|
78
|
+
step that writes a script and runs it in the same line is refused, so that the file read is the
|
|
79
|
+
file run. What `Start-Process` is asked to start is judged like any other program on the line.
|
|
80
|
+
|
|
81
|
+
**Nothing is downloaded on the bot's own account.** A step that fetches from the internet —
|
|
82
|
+
`Invoke-WebRequest`, `Invoke-RestMethod`, `curl`, `wget`, `Start-BitsTransfer`, the .NET web
|
|
83
|
+
clients, `ftp`/`scp`, a package named by URL or git address, `npx` of a tool not installed in the
|
|
84
|
+
project, `docker pull` — is neither refused nor run: it waits on the approval screen, in every
|
|
85
|
+
mode, unattended included, and "run the rest without asking" does not answer it. A check that
|
|
86
|
+
fetches is refused, since a check runs with nobody asked. Requests to this machine (`localhost`,
|
|
87
|
+
`127.0.0.1`) and the project's own package managers are not held. `npm install` runs with
|
|
88
|
+
`ignore-scripts`: a package's install script is code fetched and executed unread, and it does not
|
|
89
|
+
run here.
|
|
90
|
+
|
|
91
|
+
**A step sees a named environment, not the bot's.** Commands are given the variables Windows and
|
|
92
|
+
the toolchains need (`src/exec/stepEnv.ts`) plus whatever `execution.passEnv` names — never the
|
|
93
|
+
API token, never whatever else the operator's shell held. The runner's own git runs with hooks,
|
|
94
|
+
fsmonitor and optional locks off, so nothing a task placed in the repository runs as the runner.
|
|
95
|
+
|
|
96
|
+
### How a run is regulated
|
|
97
|
+
|
|
98
|
+
Six rules decide what a step may do, and each is recorded with the run that it governed:
|
|
99
|
+
|
|
100
|
+
- **Say where the bot runs, and an unattended run needs an answer.** `execution.isolation` is what
|
|
101
|
+
you have arranged — `none`, `separate-account`, `sandbox` or `vm` — and it defaults to `none`,
|
|
102
|
+
because that is the truth on a machine where nobody has arranged anything. It is recorded as a
|
|
103
|
+
claim, never as a finding: a process cannot see the boundary it is inside. Beside the claim the
|
|
104
|
+
runner records what it *can* read — the account, and whether the process holds High or System
|
|
105
|
+
integrity — and says so when the two disagree. While `isolation` is `none`, an unattended run
|
|
106
|
+
will not start: nobody watching and nothing containing is the one combination this refuses to be.
|
|
107
|
+
Set it under **Settings → Execution → Where the bot runs**, and `cop doctor` and the System page
|
|
108
|
+
show the same assessment. A fresh install says `none`, so the unattended **Run sessions** button
|
|
109
|
+
is refused until you have arranged something and said so; **Step by step** is unaffected.
|
|
110
|
+
|
|
111
|
+
- **The chat cannot give this runner a file.** There is one kind of step, `command`, and nothing
|
|
112
|
+
the chat attaches is fetched, saved or run. This is not a setting that could be turned back on:
|
|
113
|
+
the step type does not exist and neither does the code that fetched one. A process that receives
|
|
114
|
+
a file and executes it is a loader whatever it meant by it, and that is precisely the shape a
|
|
115
|
+
security team opened an incident about. Work too long for one line is written **from a command
|
|
116
|
+
step** — `Set-Content` with a here-string — and run in the next, so its contents passed through a
|
|
117
|
+
step that was read and screened and it sits in the project where it can be read afterwards.
|
|
118
|
+
- **What comes back is data, never instructions.** The loop reads command output back into the
|
|
119
|
+
chat, and most output was not written by this machine: it is the contents of files, logs, commit
|
|
120
|
+
messages and pages that other people wrote. Any of it can carry text shaped like an order, and a
|
|
121
|
+
model that obeys it is doing what whoever edited that file wanted — which is the whole method of
|
|
122
|
+
an indirect prompt injection, since the attacker cannot reach the conversation and so leaves
|
|
123
|
+
something where it will be read. Both contracts forbid acting on it and require it to be reported
|
|
124
|
+
instead, and **every results file repeats the boundary above the first line of output**, because
|
|
125
|
+
a contract is sent once and the early turns of a long conversation fall out of what the model can
|
|
126
|
+
see. This is guidance to a model, not a control: it is the reason the allowlist, the refused
|
|
127
|
+
techniques and confirm mode exist underneath it.
|
|
128
|
+
- **Only the declared toolchain runs.** `execution.allowedPrograms` names the programs a command
|
|
129
|
+
may start — the shells, the JavaScript, .NET, Java, Python, Go and Rust tools, `git`, and a few
|
|
130
|
+
Windows utilities. A command that starts anything else is refused and sent back with the reason.
|
|
131
|
+
An empty list turns the gate off. This is a floor against the unknown binary, not a boundary:
|
|
132
|
+
allowing `node` allows `node -e`.
|
|
133
|
+
- **Autonomy is graded.** An unattended run carries strictly more restrictions than a watched one,
|
|
134
|
+
because the thing that makes a watched run safe is a person reading each line. Unattended
|
|
135
|
+
requires a non-empty allowlist, and refuses an allowed program used to evaluate a string
|
|
136
|
+
(`node -e`, `python -c`) or a shell wrapped in a shell (`cmd /c …`) — the forms that escape the
|
|
137
|
+
allowlist by construction.
|
|
138
|
+
- **An administrator can set a floor the operator cannot lower.** A `policy.lock.json` beside the
|
|
139
|
+
configuration may forbid unattended runs, cap the allowlist and add deny patterns. Every field
|
|
140
|
+
only ever tightens, and the runner never writes the file. A machine without one behaves exactly
|
|
141
|
+
as before.
|
|
142
|
+
|
|
143
|
+
### Only the project folders
|
|
144
|
+
|
|
145
|
+
Every command a task runs — the implementer's steps and checks, the reviewer's steps and the checks
|
|
146
|
+
it derives — is held to the **project folders**: the session's own folder and every project
|
|
147
|
+
registered in Settings, so a test suite may still start the application from the repository next
|
|
148
|
+
to it. Refused before it runs:
|
|
149
|
+
|
|
150
|
+
- a path outside them — absolute, a network share, `..` that climbs out, a `cd` to the root — and
|
|
151
|
+
anything built from the operator's profile or the system: `~`, `$HOME`, `$env:USERPROFILE`,
|
|
152
|
+
`$env:APPDATA`, `$env:TEMP`, `%TEMP%` and the rest. **Reads as well as writes**: what a step prints
|
|
153
|
+
goes to the chat, so reading a key outside the project is sending it there;
|
|
154
|
+
- the registry and the other drives that are not files (`HKLM:`, `HKCU:`, `Cert:`);
|
|
155
|
+
- managing the computer: services, the network and firewall, local users, the clock, Windows
|
|
156
|
+
features, disks, machine-wide PowerShell modules;
|
|
157
|
+
- installs that land outside the project — `npm -g`, `dotnet tool -g`, `cargo install`,
|
|
158
|
+
`go install`, `pip install` into the machine's Python. The same tools installed into the project
|
|
159
|
+
(`npm install -D`, a local tool manifest, a `.venv`) stay open.
|
|
160
|
+
|
|
161
|
+
A check's own folder and the file a file check reads are held to the same folders, and a relative
|
|
162
|
+
file path is read from the project rather than from wherever the runner was started. A check the
|
|
163
|
+
reviewer derives that reaches outside is dropped rather than kept: the finding stands, the check
|
|
164
|
+
does not. Every run records the folders it was confined to in its `policy.json`.
|
|
165
|
+
|
|
166
|
+
**What this cannot do.** It reads what a step *says*, which is exactly the point — whatever the chat
|
|
167
|
+
proposes that reaches outside the project is not run. It cannot see what a program does once it is
|
|
168
|
+
running: `node build.js`, `npm run x` and a test suite can read and write anywhere the account can,
|
|
169
|
+
and so can a path assembled from variables at run time. That confinement is the operating system's
|
|
170
|
+
to give, and it is the real one:
|
|
171
|
+
|
|
172
|
+
1. Create a **standard** (not administrator) local Windows account for the bot, and sign it in to
|
|
173
|
+
Copilot once with `cop login`.
|
|
174
|
+
2. Give it modify rights on the project folders and nothing else:
|
|
175
|
+
`icacls "C:\Projects\my-app" /grant botuser:(OI)(CI)M`.
|
|
176
|
+
3. **Check what it already has.** Folders created directly under `C:\` usually let every signed-in
|
|
177
|
+
user modify them by default, which would hand the bot every project there, not just yours to
|
|
178
|
+
give. Look with `icacls C:\Projects` and remove the broad grant where it should not be.
|
|
179
|
+
4. Your own profile — `.ssh`, the browser, OneDrive — is private to you by default; confirm the bot
|
|
180
|
+
account cannot open `C:\Users\<you>`.
|
|
181
|
+
5. Run the bot as that account, and set **Settings → Execution → Where the bot runs** to
|
|
182
|
+
*A separate low-privilege Windows account*.
|
|
183
|
+
|
|
184
|
+
Windows Sandbox, with only the project folders mapped in, gives the same guarantee with less to set
|
|
185
|
+
up and nothing left behind.
|
|
186
|
+
|
|
187
|
+
### The local API is not open to everything on the machine
|
|
188
|
+
|
|
189
|
+
The API binds `127.0.0.1` only, and that was once taken to mean it needed no authentication. It
|
|
190
|
+
did. Two callers were always reachable: any page the operator has open in their ordinary browser,
|
|
191
|
+
which can issue requests to `127.0.0.1:4000` — CORS stops it reading the reply, which is no comfort
|
|
192
|
+
once a request has started a session — and any other process on the machine. So every request is
|
|
193
|
+
checked three ways before it reaches a route:
|
|
194
|
+
|
|
195
|
+
- the **`Host`** header must name this loopback port, which is what catches DNS rebinding — the
|
|
196
|
+
attacker's domain made to resolve to `127.0.0.1` is same-origin to the browser but still carries
|
|
197
|
+
its own name here;
|
|
198
|
+
- a present **`Origin`** must be the configured UI, since only a browser sets it and a page cannot
|
|
199
|
+
forge it;
|
|
200
|
+
- a per-install **token**, generated on first start into `data/api-token` (git-ignored), sent as
|
|
201
|
+
`Authorization: Bearer`, `x-cop-token`, or a `token` query parameter for the two cases that
|
|
202
|
+
cannot carry a header — `EventSource` and a plain `<a download>`.
|
|
203
|
+
|
|
204
|
+
`npm start` creates the token before either process starts and hands it to the web process only —
|
|
205
|
+
not to the API, whose environment command steps used to inherit, so a step never sees it; a step
|
|
206
|
+
that names the API's or the UI's port is refused. The web server binds `127.0.0.1` only. `GET /api/health` stays open so that "is it up yet" is still
|
|
207
|
+
answerable. To rotate the token, delete the file and restart.
|
|
208
|
+
|
|
209
|
+
The `data` folder — token, settings, sessions, the level-1 contract — is narrowed at start to the
|
|
210
|
+
operator's account and SYSTEM, and the API refuses to start while any other account can open it:
|
|
211
|
+
under `C:\Projects` a folder otherwise inherits *Authenticated Users: Modify*, which is every
|
|
212
|
+
account on the machine. A step never sees the token (see above) and is refused for naming the API's
|
|
213
|
+
ports or the data folder.
|
|
214
|
+
|
|
215
|
+
This does not contain a process already running as the operator — nothing at this layer can, since
|
|
216
|
+
that process can read the file. It moves the API from "anything on this machine, and several things
|
|
217
|
+
off it" to "the operator's own account, through a browser that was handed the key".
|
|
218
|
+
|
|
219
|
+
Every task writes `policy.json` into its run folder and a `POLICY` block into its log: the mode,
|
|
220
|
+
the allowlist and its digest, that the chat cannot supply files, digests of the deny list and of the
|
|
221
|
+
built-in refusals, whether a lock was in force and what it changed, and the account the run used.
|
|
222
|
+
So "what was this permitted to do at the time" is answered from the run folder rather than from a
|
|
223
|
+
configuration file that has been edited since.
|
|
224
|
+
|
|
225
|
+
### Before a company deploys it
|
|
226
|
+
|
|
227
|
+
A compliance review against the common frameworks (CIS Controls v8, NIST SP 800-53, ISO/IEC
|
|
228
|
+
27001 Annex A, the Microsoft baselines) was run on 2026-09-27. Nothing in the software is a
|
|
229
|
+
hidden mechanism; what follows is what a company has to decide, approve or configure before it
|
|
230
|
+
runs this on managed laptops, because no code change can decide it for them.
|
|
231
|
+
|
|
232
|
+
**Approvals to obtain.**
|
|
233
|
+
|
|
234
|
+
- *Identity and licensing.* The bot drives a licensed person's Microsoft 365 Copilot through
|
|
235
|
+
Edge, not through the API. Microsoft documents the API as the supported way to automate
|
|
236
|
+
Copilot; driving the web client is not documented as supported, and a per-user licence is
|
|
237
|
+
not meant to be shared or driven by a service identity. Ask the Microsoft contact whether the
|
|
238
|
+
intended use is acceptable under the licence, and decide which person's identity signs in —
|
|
239
|
+
one licensed identity per operator, never a shared one. Every prompt, upload and Purview
|
|
240
|
+
record is attributed to that identity.
|
|
241
|
+
- *Conditional Access.* The bot's Edge runs a profile of its own under
|
|
242
|
+
`%LOCALAPPDATA%\copilot-operator`, started by `node.exe` with Playwright's debugging channel.
|
|
243
|
+
A tenant's sign-in frequency, compliant-device and managed-browser policies may refuse it or
|
|
244
|
+
pause it for sign-in; and an Edge policy that forbids remote debugging or forces extensions
|
|
245
|
+
stops it outright. Test on one managed device before rolling out; where the baseline cannot
|
|
246
|
+
be relaxed, run it in a VM or Windows Sandbox outside the baseline's scope.
|
|
247
|
+
- *Data protection.* See "Where the reports go" above. Where the law requires it, record the
|
|
248
|
+
processing (a DPIA where automated tooling that stores every command's output and sends it to
|
|
249
|
+
an AI service triggers one; works-council consultation where tooling that records employees'
|
|
250
|
+
actions requires it). Mirrored source may carry personal data in fixtures and exports.
|
|
251
|
+
- *AI-use policy.* Commands written by a language model execute on the endpoint. Confirm mode
|
|
252
|
+
keeps a person on every line; unattended mode removes that person and is the decision most
|
|
253
|
+
AI-governance policies want recorded. `policy.lock.json` can forbid it machine-wide.
|
|
254
|
+
- *Security operations.* Tell the SOC what to expect: `node.exe` starting PowerShell (every
|
|
255
|
+
step, as `-Command` text, which PowerShell logging records verbatim) and `taskkill`; Edge
|
|
256
|
+
started by `node.exe` over a debugging pipe; updates pulled from a git remote. Register the
|
|
257
|
+
install so the process chain is known rather than investigated.
|
|
258
|
+
|
|
259
|
+
**Configuration to set** — as an administrator, in `%ProgramData%\copilot-operator\policy.lock.json`,
|
|
260
|
+
which the operator cannot edit (the same file beside the install is honoured too):
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"maxMode": "confirm",
|
|
265
|
+
"requireIsolation": true,
|
|
266
|
+
"allowDesktopMirror": false,
|
|
267
|
+
"allowEnvFiles": false,
|
|
268
|
+
"passEnv": [],
|
|
269
|
+
"allowedPrograms": ["pwsh", "powershell", "cmd", "node", "npm", "npx", "git", "dotnet"],
|
|
270
|
+
"update": { "requireSigned": true, "remote": "https://git.example.com/tools/copilot-operator.git" }
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Every field only tightens; `update` makes `npm run update` insist on signed commits and refuse
|
|
275
|
+
any other remote. Keep `data/` and `runs/` on a local disk (`COP_DATA_DIR`, `runsDir`), set
|
|
276
|
+
`runsRetentionDays`, and fork the repository internally so updates come from a remote the
|
|
277
|
+
company controls.
|
|
278
|
+
|
|
279
|
+
**Keeping it patched.** Dependencies are pinned in `package-lock.json`; `npm audit` reports
|
|
280
|
+
known vulnerabilities in them, and `npm run update` brings in whatever the remote has fixed.
|
|
281
|
+
Node.js and Edge are patched by the machine's own update process. Nothing here watches for
|
|
282
|
+
end-of-support runtimes — that is the deploying company's asset management.
|
|
283
|
+
|
|
284
|
+
**Stopping it.** "Stop" on a session cuts in after the current step and ends the task
|
|
285
|
+
`aborted`; "Pause" lets the task in flight finish and holds the queue. Ctrl+C in the `npm start`
|
|
286
|
+
terminal stops the API and the UI; processes a step started are stopped with the task (asked
|
|
287
|
+
first, forced after a few seconds), and the run log names any it could not. To end the bot's
|
|
288
|
+
access: `cop logout` signs its profile out, deleting `data/api-token` and restarting rotates the
|
|
289
|
+
API key, `cop login --fresh` deletes the profile.
|
|
290
|
+
|
|
291
|
+
**Where it does not fit.** On a fleet that enforces PowerShell Constrained Language Mode the
|
|
292
|
+
bot's own probes degrade gracefully (permissions are read through `Get-Acl`; elevation reads as
|
|
293
|
+
unknown), but the model's steps themselves run constrained, and most builds will not. That is a
|
|
294
|
+
fleet to run the bot outside of, in a VM, not one to request exceptions on.
|
|
295
|
+
|
|
296
|
+
### What an update trusts
|
|
297
|
+
|
|
298
|
+
`npm run update` fetches commits, fast-forwards onto them, installs what the lockfile names and
|
|
299
|
+
builds the result — and that result is the thing that runs commands on this machine. Its trust
|
|
300
|
+
boundary is one sentence: **whoever controls the remote controls this bot.** Three things make that
|
|
301
|
+
checkable rather than implied.
|
|
302
|
+
|
|
303
|
+
- **The remote is pinned.** The address a checkout last updated from is recorded in
|
|
304
|
+
`data/update-remote`. If `origin` now points somewhere else the update stops before anything is
|
|
305
|
+
fetched, prints both addresses, and asks. Trust on first use — the first update records what is
|
|
306
|
+
there. Accept a deliberate move with `npm run update -- --accept-remote`.
|
|
307
|
+
- **Signatures, if your fork has them.** `npm run update -- --require-signed` refuses a HEAD that
|
|
308
|
+
git cannot verify, and tells an unsigned commit apart from a bad signature. Off by default,
|
|
309
|
+
because this project does not sign its commits and a check that always fails is a check that gets
|
|
310
|
+
removed; it is here so an organisation whose fork *is* signed can make it mean something.
|
|
311
|
+
- **Every update is written down.** A line in `data/update-log.jsonl`: when, from which remote,
|
|
312
|
+
which commit to which, how many came in, and whether a signature was demanded. `data/` is
|
|
313
|
+
git-ignored, so the record survives the pull it describes.
|
|
314
|
+
|
|
315
|
+
None of this verifies what the code *does*. A signed commit from a compromised maintainer is a
|
|
316
|
+
signed commit. It narrows "anything that can reach the network" to "whoever holds the remote this
|
|
317
|
+
checkout was installed from", and says so out loud.
|
|
318
|
+
|
|
319
|
+
**The honest limit.** Everything above except the first rule is pattern matching and configuration.
|
|
320
|
+
It raises the cost of the ordinary accident and the ordinary injected instruction; it is not a
|
|
321
|
+
security boundary, and a determined attacker with a language model to write for them will find a
|
|
322
|
+
phrasing none of it anticipated. The boundary is the first rule — a separate low-privilege Windows
|
|
323
|
+
account, Windows Sandbox or a VM — and the runner can record whether you have arranged one, refuse
|
|
324
|
+
to run unwatched until you have, and never do it for you.
|
|
325
|
+
|
|
326
|
+
## Requirements
|
|
327
|
+
|
|
328
|
+
- Windows 10 or 11
|
|
329
|
+
- Node.js 20 or newer
|
|
330
|
+
- Microsoft Edge, already installed
|
|
331
|
+
- A Microsoft 365 Copilot account, signed in once by the user
|
|
332
|
+
|
|
333
|
+
## Installing it in a project
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
npm install --save-dev copilot-operator
|
|
337
|
+
npx cop login --account you@tenant.org # sign in once; the Edge profile is reused across projects
|
|
338
|
+
npx cop start --open # the API and the interface, on http://127.0.0.1:4000/
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Installed this way, the bot belongs to the project it is installed in. Its records — settings,
|
|
342
|
+
sessions, the API key, every run's logs — go to `.copilot-operator/` in the project, which carries
|
|
343
|
+
its own `.gitignore`, so the project's git never sees it and the bot's version control finds a
|
|
344
|
+
clean tree. The interface ships prebuilt and is served by the same process on one port; there is no
|
|
345
|
+
second server and nothing is compiled on the machine. `npx cop start --port 4100` picks another
|
|
346
|
+
port. `npm update copilot-operator` updates it; `npm run update` is for a clone.
|
|
347
|
+
|
|
348
|
+
The bot is then inside the folders its own steps may write, so a step that reaches for
|
|
349
|
+
`node_modules/copilot-operator`, for `.copilot-operator`, or that installs, updates or removes the
|
|
350
|
+
bot as a package is refused. What no rule can see is a step that rewrites the project's
|
|
351
|
+
`package.json` with a different version of the bot for a later `npm install` to fetch; the commit
|
|
352
|
+
the bot makes after the task shows such a change, and it is worth a look before the next install.
|
|
353
|
+
|
|
354
|
+
## Releasing a new version
|
|
355
|
+
|
|
356
|
+
Publishing is done by GitHub Actions (`.github/workflows/publish.yml`), not from a laptop:
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
npm version patch # or minor / major: updates package.json, commits, tags vX.Y.Z
|
|
360
|
+
git push --follow-tags # the tag starts the workflow: typecheck, checks, build, publish
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
The workflow publishes with npm's Trusted Publishing — no npm token is stored anywhere; npm accepts
|
|
364
|
+
the package because it comes from this repository's `publish.yml` — and with provenance, so the
|
|
365
|
+
package page on npm says which commit and which run built it. Set up once, after the first manual
|
|
366
|
+
`npm publish`: on npmjs.com, the package's **Settings → Trusted Publisher → GitHub Actions**, with
|
|
367
|
+
repository `ivan-markov-666/copilot-operator` and workflow `publish.yml`. A tag whose version does
|
|
368
|
+
not match `package.json` stops the workflow before anything is built.
|
|
369
|
+
|
|
370
|
+
## Working on it from a clone
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
npm install # root and the web workspace
|
|
374
|
+
npx tsx src/cli.ts login --account you@tenant.org # sign in once; the Edge profile is reused
|
|
375
|
+
npx tsx src/cli.ts doctor # check the machine
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
The bot never types credentials. `login` opens Edge, waits for the chat to appear, checks
|
|
379
|
+
that the right account is signed in, and stores nothing but the browser profile.
|
|
380
|
+
|
|
381
|
+
### The web UI
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
npm start # builds the API, then runs it and the UI together; Ctrl+C stops both
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Then open http://localhost:3210. `npm run dev` does the same and opens the browser for you.
|
|
388
|
+
The two can also run separately: `npm run api` (NestJS on 127.0.0.1:4000) and `npm run web`
|
|
389
|
+
(Next.js on localhost:3210).
|
|
390
|
+
|
|
391
|
+
Create a session, add tasks with their level 2 instructions, press Run, approve each step
|
|
392
|
+
from the page, read the summary when it finishes. See [`docs/ui.md`](docs/ui.md).
|
|
393
|
+
|
|
394
|
+
### Removing it from a machine
|
|
395
|
+
|
|
396
|
+
Nothing is installed system-wide, nothing runs at start-up, no service and no scheduled task
|
|
397
|
+
exist. What the software leaves, and where:
|
|
398
|
+
|
|
399
|
+
| What | Where | Remove |
|
|
400
|
+
|---|---|---|
|
|
401
|
+
| settings, sessions, the API token, level-1 and context texts | `data/` in the checkout | delete the folder |
|
|
402
|
+
| every run's logs, reports and failure screenshots | `runs/` in the checkout | delete the folder (or set `runsRetentionDays`) |
|
|
403
|
+
| backups taken by `npm run update` | `data-backups/` in the checkout | delete the folder |
|
|
404
|
+
| the bot's own Edge profile, with its Microsoft 365 session | `%LOCALAPPDATA%\copilot-operator\edge-profile` | sign out in that window (`cop login --fresh` deletes it) and delete the folder |
|
|
405
|
+
| the API key the browser was handed | the browser's storage for `localhost:3210` | clear site data for that origin |
|
|
406
|
+
| logs saved on request, and the optional project mirror | `Desktop\copilot-operator-logs`, the mirror folder in Settings | delete them; they may already be in OneDrive |
|
|
407
|
+
| the reports uploaded to Copilot | the account's OneDrive and Copilot chat history | delete the conversations, or let the tenant's retention run |
|
|
408
|
+
| a machine-wide policy lock, if an administrator placed one | `%ProgramData%\copilot-operator\policy.lock.json` | the administrator removes it |
|
|
409
|
+
|
|
410
|
+
The permissions the API set on `data/` and `runs/` (owner and SYSTEM only) go with the folders.
|
|
411
|
+
|
|
412
|
+
### Updating a machine that has been used
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
npm run update # back up, pull, install, build
|
|
416
|
+
npm run update -- --check # say what would happen, change nothing
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**A pull cannot lose what you have entered.** Everything this program records lives in `data/`
|
|
420
|
+
(settings, sessions, level 2 presets, a customised level 1, the organisation and work texts, the
|
|
421
|
+
model list) and `runs/` (every log, report and artifact of every task). Both are in `.gitignore`,
|
|
422
|
+
every write in `src/` goes to one of them or to the Desktop, and git does not know they exist.
|
|
423
|
+
|
|
424
|
+
What a pull *can* do is refuse to start. `npm run update` deals with each reason in turn: it
|
|
425
|
+
copies `data/` to `data-backups/<timestamp>/` before touching anything, refuses to run while the
|
|
426
|
+
bot is (swapping the code under a run in flight is the one thing here that could really break
|
|
427
|
+
something), puts any local edits to tracked files into a named stash and tells you the command to
|
|
428
|
+
get them back, pulls **fast-forward only** so no merge is ever made on your behalf, and then
|
|
429
|
+
reinstalls and rebuilds — because a `dist/` older than its `src/` runs the previous version and
|
|
430
|
+
reports the previous version's bugs.
|
|
431
|
+
|
|
432
|
+
Run it with `--check` first if you want to see what it would do. When nothing has come in it
|
|
433
|
+
installs and builds nothing; `--rebuild` forces both. It uses `npm ci`, and only when the pull
|
|
434
|
+
actually moved a dependency: `npm install` may rewrite `package-lock.json`, and a rewritten
|
|
435
|
+
lockfile is a tracked file that differs from the commit — which is one of the phantom local
|
|
436
|
+
changes this is here to stop.
|
|
437
|
+
|
|
438
|
+
**`npm warn allow-scripts ... esbuild`** on a machine whose npm requires install scripts to be
|
|
439
|
+
approved is expected and usually harmless. esbuild gets its Windows binary from an optional
|
|
440
|
+
dependency (`@esbuild/win32-x64`), which installs without any script; the blocked `postinstall`
|
|
441
|
+
only revalidates it. Check with `npx tsx --version` — if that answers, `npm run check` will run.
|
|
442
|
+
If it does not, `npm approve-scripts esbuild` and install again.
|
|
443
|
+
|
|
444
|
+
If files looked modified that you never edited, that was line endings: two Windows machines, one
|
|
445
|
+
with `core.autocrlf` set and one without. `.gitattributes` settles it.
|
|
446
|
+
|
|
447
|
+
### The terminal, for one task
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
cp run.example.yaml run.yaml # then edit it
|
|
451
|
+
npx tsx src/cli.ts run run.yaml
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Commands
|
|
455
|
+
|
|
456
|
+
| Command | What it does |
|
|
457
|
+
|---|---|
|
|
458
|
+
| `cop login` | opens Edge with the bot profile and waits for you to sign in |
|
|
459
|
+
| `cop doctor [run.yaml]` | checks Node, Edge, PowerShell, the Desktop, the config |
|
|
460
|
+
| `cop dirs <projectRoot>` | lists the directories you can select for the mirror |
|
|
461
|
+
| `cop mirror <run.yaml>` | refreshes the Desktop folder without touching the chat |
|
|
462
|
+
| `cop run <run.yaml>` | one task as a new session; `--unattended` skips the per-step prompt |
|
|
463
|
+
| `cop chat [run.yaml]` | prints the last run's conversation name and link |
|
|
464
|
+
|
|
465
|
+
Before it is built, run them as `npx tsx src/cli.ts <command>`.
|
|
466
|
+
|
|
467
|
+
**Testing it for the first time:** follow [`docs/testing.md`](docs/testing.md). Nine steps,
|
|
468
|
+
ordered so the risk climbs slowly, starting with checks that need no account at all.
|
|
469
|
+
`run.smoke.yaml` is a ready-made read-only first run.
|
|
470
|
+
|
|
471
|
+
## How it works
|
|
472
|
+
|
|
473
|
+
| Piece | File | State |
|
|
474
|
+
|---|---|---|
|
|
475
|
+
| DOM locators for the Copilot web app | `src/transport/locators.ts` | captured from the live app |
|
|
476
|
+
| Command runner, dual timeouts, process-tree kill | `src/exec/runner.ts` | implemented, checked |
|
|
477
|
+
| Chat naming and reattach after re-login | `src/transport/chatSession.ts` | implemented, checked |
|
|
478
|
+
| Covering message for the results file | `src/protocol/reporter.ts` | implemented, checked |
|
|
479
|
+
| Desktop folder resolution and guard rails | `src/context/contextFiles.ts` | implemented, checked |
|
|
480
|
+
| Project mirror, flattened names, incremental | `src/context/projectMirror.ts` | implemented, checked |
|
|
481
|
+
| Pacing, send cap, backoff | `src/util/pacing.ts` | implemented, checked |
|
|
482
|
+
| Playwright transport | `src/transport/copilotTransport.ts` | implemented; selectors verified live |
|
|
483
|
+
| Reply parser and the JSON contract | `src/protocol/parser.ts`, `replySchema.ts` | implemented, checked |
|
|
484
|
+
| Report file, splitting, redaction | `src/exec/reportFile.ts` | implemented, checked |
|
|
485
|
+
| Deny list and the confirm gate | `src/exec/policy.ts` | implemented, checked |
|
|
486
|
+
| Config schema and loader | `src/config/schema.ts` | implemented |
|
|
487
|
+
| The loop, per task and per session | `src/orchestrator/taskRunner.ts` | run live end to end |
|
|
488
|
+
| Sessions, tasks, level 2 presets on disk | `src/session/` | implemented, checked |
|
|
489
|
+
| API | `src/api/` (NestJS) | implemented, exercised over HTTP |
|
|
490
|
+
| Web UI | `web/` (Next.js) | implemented |
|
|
491
|
+
| CLI | `src/cli.ts` | implemented |
|
|
492
|
+
|
|
493
|
+
Design and findings:
|
|
494
|
+
|
|
495
|
+
- [`docs/ui.md`](docs/ui.md) — the web UI, the API and the session model
|
|
496
|
+
- [`docs/testing.md`](docs/testing.md) — how to test it the first time, step by step
|
|
497
|
+
- [`docs/architecture.md`](docs/architecture.md) — the whole design
|
|
498
|
+
- [`docs/locators-findings.md`](docs/locators-findings.md) — what the live Copilot DOM looks like and why
|
|
499
|
+
- [`docs/tech-stack-research.md`](docs/tech-stack-research.md) — why Playwright and not the alternatives
|
|
500
|
+
- [`docs/deferred.md`](docs/deferred.md) — designed, argued and deliberately not built yet, with the reasoning kept
|
|
501
|
+
|
|
502
|
+
### What the planning persona knows
|
|
503
|
+
|
|
504
|
+
The brief the UI hands to a chat model — the Kerrigan persona — carries the names of this
|
|
505
|
+
application's own screens and controls, so it can tell the operator to press "Create the sessions
|
|
506
|
+
and tasks" rather than "import the plan". They live in `src/plan/systemGuide.ts` and are embedded
|
|
507
|
+
in the brief by `src/plan/brief.ts`, and `test/guide.check.ts` pins every one of them to
|
|
508
|
+
`web/lib/strings.ts` in both languages. Renaming a control in the interface therefore breaks that
|
|
509
|
+
check by name, and a new control the operator would ever be told to press has to be added to the
|
|
510
|
+
guide as well, or the persona does not know it exists. The same check reads the pages themselves,
|
|
511
|
+
so a label that is still in the dictionary but that nothing renders any more fails too, and it
|
|
512
|
+
holds the labels the brief quotes in its own prose to the same standard.
|
|
513
|
+
|
|
514
|
+
## Notable findings
|
|
515
|
+
|
|
516
|
+
These cost real investigation and are worth knowing before touching the code.
|
|
517
|
+
|
|
518
|
+
- **Code blocks cannot be read from the DOM.** They are virtualized and interleave line
|
|
519
|
+
numbers with the code. A valid JSON reply came back from `innerText` with a chunk missing.
|
|
520
|
+
The message-level "Copy Response" button plus clipboard access is the only safe read.
|
|
521
|
+
- **Downloads are plain anchors** with a `blob:` href and a `download` attribute, which is
|
|
522
|
+
why nothing useful appears on hover. Playwright's download event handles them.
|
|
523
|
+
- **Enter does not send.** The composer is a contenteditable span; the Send button must be
|
|
524
|
+
clicked.
|
|
525
|
+
- **A message cannot be only an attachment.** Text is mandatory, so the covering message is
|
|
526
|
+
part of the protocol rather than decoration.
|
|
527
|
+
- **Uploads go through the user's OneDrive.** The chat says so, and the attachment id carries
|
|
528
|
+
an `SPO_` prefix. That prefix is also the most reliable "upload finished" signal.
|
|
529
|
+
- **Copilot destroys `[name]:` in its own output.** `[math]::Round($x, 2)` reaches the
|
|
530
|
+
runner as `:Round($x, 2)`, even inside a fenced code block, because `[label]:` is markdown
|
|
531
|
+
for a link reference. `[pscustomobject]` and `[double]$x` survive, since the damage needs
|
|
532
|
+
the colon. The prompts forbid that form and the runner refuses a command that arrives
|
|
533
|
+
wrecked rather than executing something nobody wrote.
|
|
534
|
+
- **`lastChatMessage` is not the whole answer.** It is the answer body; the copy button is
|
|
535
|
+
its sibling. A wait built on `[data-testid="lastChatMessage"] [data-testid="CopyButtonTestId"]`
|
|
536
|
+
never completes. The anchor is the last `copilot-message-div`.
|
|
537
|
+
|
|
538
|
+
## Checks
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
npm run check # all of them
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Each one prints what it exercised rather than asserting silently, so a failure is readable.
|
|
545
|
+
There is no live-tenant test: that is the part that needs a human and a signed-in browser.
|
|
546
|
+
|
|
547
|
+
## Related
|
|
548
|
+
|
|
549
|
+
[context-picker](https://github.com/ivan-markov-666/context-picker) (MIT) — the same author's
|
|
550
|
+
VS Code and Visual Studio extension for picking which project files an LLM should see. This
|
|
551
|
+
project follows its file-naming convention, so the two produce interchangeable folders, and
|
|
552
|
+
can drive it directly for per-file selection.
|
|
553
|
+
|
|
554
|
+
## Licence
|
|
555
|
+
|
|
556
|
+
MIT. See [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { Module } from '@nestjs/common';
|
|
8
|
+
import { OperatorController } from './operator.controller.js';
|
|
9
|
+
import { OperatorService } from './operator.service.js';
|
|
10
|
+
let AppModule = class AppModule {
|
|
11
|
+
};
|
|
12
|
+
AppModule = __decorate([
|
|
13
|
+
Module({
|
|
14
|
+
controllers: [OperatorController],
|
|
15
|
+
providers: [OperatorService],
|
|
16
|
+
})
|
|
17
|
+
], AppModule);
|
|
18
|
+
export { AppModule };
|