@rubytech/create-realagent 1.0.619 → 1.0.621

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.
Files changed (23) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts.map +1 -1
  3. package/payload/platform/lib/mcp-stderr-tee/dist/index.js +11 -5
  4. package/payload/platform/lib/mcp-stderr-tee/dist/index.js.map +1 -1
  5. package/payload/platform/lib/mcp-stderr-tee/src/index.ts +10 -5
  6. package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +11 -7
  7. package/payload/platform/plugins/cloudflare/PLUGIN.md +10 -13
  8. package/payload/platform/plugins/cloudflare/mcp/dist/index.js +181 -1033
  9. package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
  10. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +117 -261
  11. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -1
  12. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +379 -903
  13. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -1
  14. package/payload/platform/plugins/cloudflare/mcp/package.json +3 -7
  15. package/payload/platform/plugins/cloudflare/references/setup-guide.md +70 -76
  16. package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +34 -82
  17. package/payload/platform/plugins/docs/PLUGIN.md +1 -1
  18. package/payload/platform/plugins/docs/references/cloudflare.md +21 -30
  19. package/payload/platform/templates/agents/admin/IDENTITY.md +8 -0
  20. package/payload/platform/templates/agents/public/IDENTITY.md +8 -0
  21. package/payload/platform/templates/specialists/agents/personal-assistant.md +9 -9
  22. package/payload/server/server.js +85 -9
  23. package/payload/platform/plugins/cloudflare/mcp/__tests__/auth-binding.test.ts +0 -195
@@ -1,112 +1,64 @@
1
1
  ---
2
2
  name: setup-tunnel
3
- description: Set up a Cloudflare Tunnel using deterministic MCP tools. All subdomain input comes from a rendered UI component the agent never synthesises or parses domain fragments from free text.
3
+ description: Set up a Cloudflare Tunnel. The agent coaches the operator through the Cloudflare dashboard and runs cloudflared CLI commands; it never reads or mutates Cloudflare account state.
4
4
  ---
5
5
 
6
6
  # Set Up Cloudflare Tunnel
7
7
 
8
- Create a Cloudflare Tunnel so the platform is accessible from the public internet. `cloudflare-setup` is the single orchestrator. Every choice the user must make is collected by a UI component that `cloudflare-setup` names in its response the agent is a courier between UI and tool.
8
+ Create a Cloudflare Tunnel so the platform is reachable from the public internet. The operator drives the Cloudflare dashboard; the agent drives `cloudflared`. The `cloudflared` CLI authenticated by cert.pem is the only path between this codebase and Cloudflare; account-state enumeration paths were removed because every prior attempt at them ended up signing the laptop into the wrong Cloudflare account.
9
9
 
10
10
  ## Rules of engagement
11
11
 
12
- - **Never ask the user for a domain, subdomain, FQDN, tunnel name, or label in chat.** Those fields come only from rendered components.
13
- - **Never synthesise tool parameters from free text.** If the user types `rogerblack.maxy.bot` in chat, do not pass any part of that string as `adminLabel`, `publicLabel`, `selectedZoneName`, or `selectedTunnelId`. Render the component, let the UI collect the value.
14
- - **Never retry `cloudflare-setup` with different parameter values on error.** Every error is structured relay the message and render whatever component the response's `data.render` field names.
15
- - **Treat every `awaiting_*` status as a render instruction.** Read `data.render`, call `render-component` with its `name` and `data` verbatim, then stop. Wait for the submission to come back as a user message before calling `cloudflare-setup` again.
16
-
17
- ## Identity and scope
18
-
19
- The bound Cloudflare account (via `cert.pem`) is the universe. Every routable zone must be a zone on that account. There is no brand-declared allowlist; whatever the account holds is fair game. The agent owns the bound account absolutely — it can add, delete, restructure as needed.
20
-
21
- Three checks the tools enforce automatically:
22
-
23
- 1. **Account binding.** `cert.pem`'s account ID must equal the one recorded in `~/{configDir}/cloudflare/account-binding.json`. Drift refuses with `reason=account-drift`. A device with no binding refuses every non-login operation with `reason=unbound-device`. The single recovery is `tunnel-login` (with `force=true` to switch accounts).
24
- 2. **Live account-zone scope.** Before writing any DNS record, the requested hostname's registrable parent must be an active zone on the bound account. Refuse with `reason=scope-mismatch` otherwise — the fix is `cf-add-zone <domain>` or pick a hostname under an existing zone.
25
- 3. **Post-flight FQDN.** The FQDN `cloudflared` actually wrote must equal the requested hostname. A mismatch refuses with `reason=post-flight-fqdn-mismatch`, attempts cleanup, and leaves recovery to `cf-rebuild`.
26
-
27
- There is no API-token path.
28
-
29
- ## User-facing language
30
-
31
- Never say cert.pem, Zone, DNS, CNAME, ingress, tunnel name, or any Cloudflare-internal terminology to the user. Use plain language: "domain", "address", "remote access", "connection".
12
+ - **The dashboard is the source of truth.** The operator's logged-in Cloudflare session is authoritative for which domains exist, which account owns them, and which addresses are already in use. The agent never second-guesses this — the codebase has no path to Cloudflare account state beyond what `cloudflared` reads from cert.pem.
13
+ - **Speak dashboard language.** Say "Cloudflare account", "domain", "address", "sign in", "browser". Never say "zone", "CNAME", "account ID", "API", "SDK", "403", "record", or any hexadecimal identifier. Internal logs may use those terms; operator-facing text must not.
14
+ - **One prescribed recovery per failure mode.** When a `tunnel-*` tool returns a structured failure, relay the single sentence the tool gave you verbatim and take it. Never offer the operator a choice of actions — the tool has already told you which is correct (IDENTITY § Questions). Never suggest "open the dashboard" as an alternative when the sentence names something specific.
15
+ - **The operator confirms before the agent re-runs.** After asking the operator to do something in the dashboard, wait for explicit confirmation ("done", "ok", "yes") before the next tool call.
32
16
 
33
17
  ## The flow
34
18
 
35
- 1. **Show the browser.** Call `render-component` with `name: "browser-viewer"` and `data: { title: "Cloudflare" }`. Skip when the viewer is already visible.
36
-
37
- 2. **Call `cloudflare-setup` with no arguments** the first time. The tool discovers current state (auth, tunnels on account, zones on account, persisted tunnel state) and returns a structured result. Parse `status` and act according to the table below.
19
+ 1. **Confirm the operator has a Cloudflare account.** If they do not, say: "You will need a Cloudflare account. Sign up free at cloudflare.com, come back and tell me when you are signed in." Wait for confirmation, then continue.
38
20
 
39
- ## Status table
21
+ 2. **Confirm the operator's domain is on their Cloudflare account.** Ask: "What domain do you want to use, and is it already on Cloudflare?" When it is not on Cloudflare yet, say: "Open Cloudflare in your browser, go to Websites → Add a site, enter the domain, and follow the dashboard's instructions for nameservers. Tell me when the domain shows as Active in the dashboard." Wait for confirmation, then continue.
40
22
 
41
- | Returned `status` | What to do |
42
- |-------------------------------|-----------|
43
- | `awaiting_auth` | Call `browser_navigate` with `data.authUrl`, then `browser_tabs` with `action: "select"` to bring it front. Tell the user: "Sign in to your Cloudflare account in the browser and click Authorize. Let me know when you're done." Stop. When they confirm, call `cloudflare-setup` again with no arguments. |
44
- | `awaiting_nameservers` | Relay the message verbatim. The user updates nameservers at their registrar, confirms, and you call `cloudflare-setup` again. |
45
- | `awaiting_tunnel_selection` | The account has existing tunnels. Call `render-component` with `data.render` verbatim. When the user submits, the payload is `{"selectedTunnelId":"<value>"}`. Call `cloudflare-setup` with `{ selectedTunnelId: "<value>" }`. The literal value `"__new__"` means the user chose to create a new tunnel. |
46
- | `awaiting_zone_selection` | More than one active domain on the account. Call `render-component` with `data.render` verbatim. When submitted, payload is `{"selectedZoneName":"<value>"}`. Call `cloudflare-setup` with `{ selectedZoneName: "<value>" }`. |
47
- | `awaiting_zone_add_dashboard` | Zero domains on the account. Relay the message. The user adds a domain at `cloudflare.com` in the VNC browser, confirms, and you call `cloudflare-setup` again with no arguments. |
48
- | `awaiting_cleanup_confirmation` | The bound Cloudflare account has artefacts that don't belong to the chosen domain (other zones, other tunnels, stale DNS pointing to dead tunnels). Call `render-component` with `data.render` verbatim — this is a `confirm` component listing what will be deleted. When the user approves, the payload is a JSON string echoing `selectedZoneName` plus `cleanupConfirmed: true`. Call `cloudflare-setup` with the payload's fields. When the user rejects, relay the rejection and stop — do not retry with different parameters. |
49
- | `awaiting_labels` | Call `render-component` with `data.render` verbatim — this is the `tunnel-route-picker`. When the user submits, the payload is `{"adminLabel":"...","publicLabel":"..."}` (public may be absent). Call `cloudflare-setup` with `{ adminLabel: ..., publicLabel?: ... }`. |
50
- | `tunnel-name-taken` | The chosen admin address is already used by another device on the same account. Relay the message and call `render-component` with `data.render` verbatim — this re-renders the picker with an inline error on the Admin field. Proceed as for `awaiting_labels` when the user resubmits. |
51
- | `label-taken` | DNS for one of the requested addresses is already owned by another device. Relay the message and call `render-component` with `data.render` verbatim — the picker re-renders with the error on the offending field. |
52
- | `awaiting_password` | Relay the message. The user sets the remote-access password in their own browser (not the VNC browser) at the `setupUrl`. Wait, then call `cloudflare-setup` again. **Important:** always wrap `setupUrl` in backticks — double underscores in `/__remote-auth/setup` are markdown-escaped otherwise. |
53
- | `complete` | Relay the message with the admin URL (and public URL if present). Done. |
54
- | `error` | Relay the message. Do not retry with mutated parameters. |
23
+ 3. **Run `tunnel-login`.** The tool opens a Cloudflare sign-in URL in the VNC browser. Say: "Sign in to the Cloudflare account that owns `<domain>` — the account name is in the top-left of the Cloudflare page, next to the little orange cloud. Click Authorize when ready." When the operator confirms, call `tunnel-login` again. The tool reports one of four states:
24
+ - **"Sign-in complete."** — move to step 4.
25
+ - **"Sign-in in progress…"** the operator has not finished yet. Wait for explicit confirmation and call again.
26
+ - **"Sign-in failed the browser didn't open on the laptop. Restarting."** the tool has already respawned login. Relay the new sign-in URL and wait for the operator.
27
+ - **"Sign-in ended without saving the cert. Restarting."** same respawn; relay the new URL.
55
28
 
56
- ## Prerequisites
29
+ 4. **Run `tunnel-create`.** Ask the operator what sub-addresses they want. The tool creates the tunnel and routes each address via `cloudflared`. Refusals:
30
+ - **`hostname-zone-not-routable`** — the domain is not on the signed-in Cloudflare account. Relay the refusal sentence verbatim.
31
+ - **`post-flight-fqdn-mismatch`** — `cloudflared` wrote the address under a different domain than asked. Relay the refusal sentence verbatim.
57
32
 
58
- Before starting, call `tunnel-status` to assess current state.
33
+ 5. **Run `tunnel-enable`.** Starts the tunnel daemon and verifies the admin URL is reachable through Cloudflare. Reports success with the live URLs.
59
34
 
60
- - **Tunnel already running with no DNS warnings:** Confirm to the user and stop.
61
- - **Tunnel running but DNS NOT RESOLVING warning:** Call `cloudflare-setup` it will re-route DNS idempotently.
62
- - **cloudflared not installed:** `cloudflare-setup` installs it automatically.
63
- - **`bound: false` (cert/binding missing or mismatched):** Full flow required. Call `cloudflare-setup` and follow the status table.
35
+ 6. **Run `tunnel-status`.** The e2e probe should report `healthy: true`. When it is `false`, the tool's trailing text names exactly one next action — relay it verbatim. The recovery sentence handles the following `unhealthyReason` values deterministically:
36
+ - `bound-account-does-not-own-hostname` the operator must sign into the correct Cloudflare account; the sentence names the domain.
37
+ - `hostname-probes-failed` with `tunnel-not-matched` / `cname-points-elsewhere` the agent calls `tunnel-add-hostname` for each configured hostname to re-point DNS.
38
+ - `hostname-probes-failed` with `dns-missing` the agent calls `tunnel-add-hostname` to create DNS.
39
+ - `hostname-probes-failed` with `edge-unreachable` — transient; wait, do not re-probe or open the dashboard.
40
+ - `not-running` — ask the operator for permission to enable the tunnel.
41
+ - `no-tunnel-configured` — run `tunnel-create`.
64
42
 
65
- ## Add alias domain
43
+ ## Adding an alias address
66
44
 
67
- An alias domain (e.g. `maxy.chat`) serves the public chat directly the URL stays as the alias domain in the browser bar. Alias domains go through `tunnel-add-hostname`, which is a single-hop operation independent of `cloudflare-setup`.
45
+ An alias address (e.g. `maxy.chat`) serves the public chat directly. The alias's domain must be on the same Cloudflare account the laptop is signed into.
68
46
 
69
- ### Prerequisites
70
-
71
- - A working tunnel (the main setup flow must be complete)
72
- - The alias domain must be an Active zone on the Cloudflare account
73
- - The tunnel ID (from `tunnel-status`)
74
-
75
- ### Flow
76
-
77
- 1. Confirm the alias domain with the user.
78
- 2. Call `cf-zone-status`. The alias domain must appear as Active. If not, guide the user through adding it (for activation, the main flow's `awaiting_zone_add_dashboard` / `awaiting_nameservers` handling applies).
79
- 3. Call `tunnel-status` for the tunnel ID.
80
- 4. Call `tunnel-add-hostname` with the alias domain and tunnel ID.
81
- 5. Verify with `dns-lookup`. Tell the user: "Your public chat is now live at https://{alias_domain}." DNS propagation takes 1-5 minutes.
47
+ 1. Ask the operator: "Is `<alias>` on the same Cloudflare account this laptop is signed into?" When it is not, say: "Open Cloudflare in your browser, go to Websites → Add a site, and add `<alias>`. Tell me when it shows as Active." Wait for confirmation.
48
+ 2. Run `tunnel-add-hostname` with the alias and the tunnel UUID from `tunnel-status`. Refusals carry the same verbatim recovery sentences as step 4.
49
+ 3. Verify with `tunnel-status`. DNS propagation takes 1-5 minutes.
82
50
 
83
51
  ## Reinstalling / upgrading the platform
84
52
 
85
53
  When re-running the installer, pass `--hostname` and `--port` to preserve the device's current identity:
86
54
 
87
55
  1. Call `system-status` to get the current `hostname` and `port`.
88
- 2. Derive the brand package (e.g., `Maxy` → `@rubytech/create-maxy`, `Real Agent` → `@rubytech/create-realagent`).
89
- 3. Run: `npx -y @rubytech/create-realagent --hostname muvin --port 19400`
90
-
91
- Without these flags, the installer falls back to detection which is unreliable when the OS hostname was previously reset to a brand default.
92
-
93
- ## Diagnosis and recovery
94
-
95
- When something is wrong (refusal, dead URL, operator reports trouble):
96
-
97
- - **`cf-verify`** — non-mutating audit. Returns the bound account's zones, tunnels, and CNAMEs plus device-side cert / binding / tunnel state. Also returns `pollution` (default no-intent view: account artefacts the device doesn't reference). Run this first; works in any state.
98
- - **`cf-rebuild`** — nuclear cleanup of the bound account. Deletes every tunnel, CNAME, and zone NOT in the `preserve` set. Defaults `preserve` to whatever the device's `tunnel.state` and `alias-domains.json` reference. When `preserve.cnames` is unset, CNAMEs on preserved zones survive EXCEPT those pointing to `<deletedTunnelId>.cfargotunnel.com` (stale tunnel pointers are always scrubbed). Pass explicit `preserve.zones`, `preserve.tunnelIds`, or `preserve.cnames` to override. Refuses with `reason=no-intent` when called with no `preserve` AND no device-persisted intent — pass an explicit empty preserve (`{ zones: [], tunnelIds: [] }`) to state "nuke everything". `dryRun=true` plans without mutating. The agent owns the bound account absolutely — anything not in `preserve` is treated as junk.
99
-
100
- Recovery vocabulary by refusal reason:
101
- - `account-drift` or `unbound-device` → `tunnel-login force=true` (clears cert + binding) then re-authenticate.
102
- - `scope-mismatch` → `cf-add-zone <domain>` (if the user owns the domain), or pick a hostname under an existing zone.
103
- - `post-flight-fqdn-mismatch` → `cf-rebuild` to reconcile.
104
- - `no-intent` → pass explicit `preserve` when calling `cf-rebuild` directly, or use `cloudflare-setup` (which always passes explicit intent).
56
+ 2. Derive the brand package (`Maxy` → `@rubytech/create-maxy`, `Real Agent` → `@rubytech/create-realagent`).
57
+ 3. Run: `npx -y @rubytech/create-maxy --hostname muvin --port 19400`
105
58
 
106
59
  ## Important
107
60
 
108
- - **`cloudflare-setup` is the onboarding orchestrator.** Every branch decision is already encoded in its returned `status`. Do not call `cf-add-zone` or `tunnel-enable` directly from this flow `cloudflare-setup` invokes them as needed.
109
- - **Never interact with Cloudflare dashboard forms.** The only Cloudflare URL opened in the VNC browser is the auth URL from `awaiting_auth`. The dashboard is only shown to the user directly when `awaiting_zone_add_dashboard` fires.
110
- - **The user signs in themselves.** Open the URL, tell the user what to do, wait. Do not evaluate the page.
111
- - **All tunnel MCP tools are idempotent.** Safe to call after any partial failure — they read filesystem state top-to-bottom every call.
61
+ - **Never interact with Cloudflare dashboard forms on the operator's behalf.** The only Cloudflare URL opened in the VNC browser is the sign-in URL from `tunnel-login`. Everything else in the dashboard, the operator does themselves.
62
+ - **The operator signs in themselves.** Open the URL, tell them what to do, wait. The agent does not evaluate the page.
63
+ - **The tunnel tools are idempotent.** Safe to call after any partial failure they read filesystem state top-to-bottom on every call.
112
64
  - **DNS propagation takes 1-5 minutes.** Nameserver propagation takes minutes to 24 hours.
@@ -30,7 +30,7 @@ Load these when performing admin tasks or diagnosing platform behaviour:
30
30
 
31
31
  - **Platform architecture** → `references/platform.md` — how the platform works, agent types, the plugin model
32
32
  - **Platform internals** → `references/internals.md` — retrieval pipeline, embedding infrastructure, guard layers, query classification, memory-rank, graph expansion, keyword subscriptions, context assembly, inbound message screening, and tool call audit trail. Load when answering architecture questions, assessing whether a capability exists, diagnosing retrieval behaviour, or reviewing security and privacy features.
33
- - **Cloudflare** → `references/cloudflare.md` — declarative brand-zone scope, the four-step guard, `cf-verify` / `cf-rebuild` recovery flow, how to add a zone for a brand.
33
+ - **Cloudflare** → `references/cloudflare.md` — dashboard-first tunnel setup, the `cloudflared`-CLI-only tool surface, single-recovery-path (re-login) for every wrong-account failure.
34
34
  - **Deployment** → `references/deployment.md` — Pi setup, Cloudflare tunnel, start script
35
35
 
36
36
  ## References
@@ -1,51 +1,42 @@
1
- # Cloudflare Tunnel — bound account is the universe
1
+ # Cloudflare Tunnel — the dashboard is the source of truth
2
2
 
3
- Each installation has its own Cloudflare account. The user signs in via OAuth (`tunnel-login`); `cloudflared` writes `cert.pem`. The plugin binds to that account on first successful login (`account-binding.json`) and refuses to operate if the cert is later rotated under a different account.
3
+ Each installation has its own Cloudflare account. The operator signs into it via OAuth (`tunnel-login`); `cloudflared` writes `cert.pem` locally. The plugin records which account that sign-in landed on (`account-binding.json`) and refuses if the operator later signs in under a different account without explicit intent.
4
4
 
5
- The agent has absolute authority over the bound account: it can add zones, delete zones, create or delete tunnels, write or remove CNAMEs. There is no inherited "brand zone" allowlist. The user's Cloudflare account state what zones they own, what's already on it is the entire universe.
5
+ The agent never reads or mutates Cloudflare account state directly the `cloudflared` CLI, authenticated by `cert.pem`, is the only path to Cloudflare. Whatever the operator sees in their logged-in dashboard is the single source of truth. When something needs doing on the account side (adding a domain, deleting a stray entry, switching accounts), the agent tells the operator what to click; the operator clicks; then the agent runs the next `cloudflared` command.
6
6
 
7
7
  ## Identity
8
8
 
9
9
  | Fact | Source |
10
10
  |------|--------|
11
11
  | **Product identity** (Maxy vs Real Agent) | `brand.json` (`productName`, `configDir`) — known at install. |
12
- | **Cloudflare account identity** | `cert.pem` from OAuth (`tunnel-login`). One account per installation. |
13
- | **Account binding** | `~/{configDir}/cloudflare/account-binding.json` written on first successful login — drift detection only. |
12
+ | **Cloudflare account identity** | `cert.pem` from OAuth (`tunnel-login`). One account per laptop. |
13
+ | **Account binding** | `~/{configDir}/cloudflare/account-binding.json` — drift detection only. |
14
14
 
15
- `cf-set-token` does not exist. The only auth path is `tunnel-login`. To switch Cloudflare accounts, run `tunnel-login force=true` to clear cert + binding, then re-authenticate.
15
+ There is no token-based path. To switch Cloudflare accounts, run `tunnel-login` with `force=true`. This signs the laptop out (deletes cert + binding), then a fresh sign-in picks a different account.
16
16
 
17
- ## The flow (embarrassingly simple)
17
+ ## The flow
18
18
 
19
19
  1. Operator: "Set up Cloudflare for me."
20
- 2. Agent calls `cloudflare-setup`. The orchestrator inspects the bound account, asks the operator to pick a zone (if more than one is active), surfaces pollution (other zones, other tunnels, stale CNAMEs pointing to dead tunnels), and asks for explicit cleanup confirmation before deleting.
21
- 3. After cleanup, the orchestrator asks for admin / public subdomain labels (defaults offered).
22
- 4. Tunnel created, DNS routed, URL verified. Operator accesses the live URL.
23
- 5. Optional: alias domains (e.g. `maxy.chat`) via `tunnel-add-hostname` (must be a zone on the bound account; run `cf-add-zone` first if not).
20
+ 2. Agent confirms the operator has a Cloudflare account and a domain on that account. If either is missing, the agent tells the operator where to click in the browser, waits for confirmation, and continues.
21
+ 3. Agent runs `tunnel-login`, opens the sign-in URL in the VNC browser, tells the operator which account to pick, waits for confirmation.
22
+ 4. Agent runs `tunnel-create` with the chosen domain and admin/public sub-addresses. `cloudflared` creates the tunnel and routes DNS via the signed-in session.
23
+ 5. Agent runs `tunnel-enable`. The tool verifies the admin URL is reachable through Cloudflare before reporting success.
24
+ 6. Optional: alias addresses via `tunnel-add-hostname`.
24
25
 
25
26
  ## Tools
26
27
 
27
28
  | Tool | Use |
28
29
  |------|-----|
29
- | `cloudflare-setup` | Onboarding orchestrator. UI-driven state machine including the cleanup confirmation step. |
30
- | `tunnel-login` | OAuth only auth path. `force=true` clears cert + binding. |
31
- | `cf-verify` | Read-only snapshot of account + device + pollution (no-intent view). Run first. |
32
- | `cf-rebuild` | Nuclear cleanup. Deletes everything not in `preserve`. Refuses (`reason=no-intent`) when called with no `preserve` AND no device-persisted intent. |
33
- | `cf-add-zone` | Add a domain to the bound account. Idempotent. |
34
- | `cf-zone-status` | List zones on the bound account. |
35
- | `tunnel-create` | Create a tunnel and route DNS for chosen subdomains. Refuses hostnames not on a zone the account owns. |
36
- | `tunnel-add-hostname` | Add an alias hostname to an existing tunnel. |
30
+ | `tunnel-login` | Browser OAuth. `force=true` signs out first so a fresh sign-in can pick a different account. |
31
+ | `tunnel-install` | Install the `cloudflared` binary. |
32
+ | `tunnel-create` | Create tunnel, route DNS for chosen sub-addresses. Pre-flight + post-flight refuse when the laptop is signed into a Cloudflare account that does not own the target domain. |
33
+ | `tunnel-add-hostname` | Add an alias address. Same pre-flight/post-flight refusals as `tunnel-create`. |
37
34
  | `tunnel-enable` / `tunnel-disable` | Start / stop the tunnel daemon. |
38
- | `tunnel-status` | Operational state including `bound`. |
39
- | `dns-lookup` | DNS diagnostics. |
35
+ | `tunnel-status` | Full state including end-to-end probe of every configured address. `healthy: true` requires every address to probe `ok`. `boundAccountOwnsHostnames: false` means the laptop is running a tunnel nothing from the internet is reaching — almost always "signed into the wrong Cloudflare account." |
36
+ | `dns-lookup` | DNS diagnostics against public resolvers. |
40
37
 
41
38
  ## When something is wrong
42
39
 
43
- - **Refusal `account-drift` or `unbound-device`** → `tunnel-login force=true`, re-authenticate.
44
- - **Refusal `scope-mismatch`** → the requested hostname's parent zone isn't on the bound account. Run `cf-add-zone <domain>` (if user owns it) or pick another zone.
45
- - **Refusal `post-flight-fqdn-mismatch`** defence-in-depth fired; cleanup attempted. Run `cf-rebuild` to reconcile.
46
- - **Refusal `no-intent`** → `cf-rebuild` was called with no `preserve` on a device with no persisted tunnel or alias. Pass explicit `preserve` (empty arrays are valid "nuke everything" intent) or drive cleanup through `cloudflare-setup` instead.
47
- - **General mess on the account** → `cf-verify` to see, then `cf-rebuild` (with `dryRun: true` first if cautious) to clean.
48
-
49
- ## What pollution means
50
-
51
- Anything on the bound account that the user's current intended state (`tunnel.state` + `alias-domains.json`) doesn't reference is junk. Old tunnels, stale CNAMEs, zones from previous setups — all candidates for deletion. The agent never assumes anything is precious; the user states intent and the agent obliterates the rest.
40
+ - **Refusal `account-drift` or `unbound-device`** → `tunnel-login force=true`, then a fresh browser sign-in.
41
+ - **Refusal `hostname-zone-not-routable`** → the domain is not on Cloudflare yet, or the laptop is signed into a Cloudflare account that does not own it. Operator opens the dashboard; when the domain has never been added, they add it via Websites Add a site under the correct account. When the domain is on a different account, they switch to the correct account using the top-left dropdown. Agent then retries.
42
+ - **Refusal `post-flight-fqdn-mismatch` or `bound-account-does-not-own-hostname`** the laptop is signed into a Cloudflare account that does not own the target domain. `cloudflared` routed the address under a different domain. Operator switches Cloudflare accounts in the browser, agent runs `tunnel-login force=true`, then retries.
@@ -58,6 +58,14 @@ Do not retry the same tool against the same target within a turn. A second ident
58
58
 
59
59
  When a tool returns a structured failure whose error content begins with an UPPERCASE_ERROR_CODE (for example `WEBFETCH_CANNOT_READ_JS_SPA`), the runtime has already determined that retrying the same tool will fail and that a substitute would launder uncertainty. Read the error's plain-English explanation, then write one or two sentences to the owner that name (a) what failed, (b) the reason in their language, and (c) the concrete actions they can take to unblock — typically pasting text or sending a screenshot. Do not silently dispatch a substitute (Playwright, research-assistant, memory-search) to continue the original instruction; that hides the failure and the owner loses the ability to judge whether the substitute's output answers their question. A verbal instruction in the current conversation is not consent — only an explicit standing policy recorded in account configuration counts, and no such mechanism exists today. Until one exists, every structured tool failure becomes a question for the owner. Wait for direction before resuming.
60
60
 
61
+ ## Questions
62
+
63
+ When you need to clarify intent before acting, follow two rules.
64
+
65
+ 1. **One-sided questions only.** Frame every clarifying question so a single-word "yes" or "no" is unambiguous. Never pose two opposing framings joined by "or" — "Should I proceed, or stop?", "Want me to do X, or not?", "Shall I run this, or do you want to?". "Yes" to such a question is unusable — you cannot tell which side was affirmed, and guessing produces the wrong action. Pick one side, ask it plainly: "Proceed?" or "Stop?" — not both.
66
+
67
+ 2. **No choice-fork when the signal is deterministic.** When a tool returns a typed failure — an enum failure-mode, an UPPERCASE_ERROR_CODE, or a populated recovery instruction — the tool has already told you which action is correct. Relay that action to the owner and take it. Do not degrade the signal into a menu ("want me to run the recovery, or do something else?"). The menu invites the wrong branch. This extends the Tool Failure Discipline above — that section covers acknowledgement and no-silent-fallback; this rule covers no-menu-when-the-answer-is-given.
68
+
61
69
  ## Tool Routing
62
70
 
63
71
  Plugins provide domain-specific tools that query their own data stores directly. `memory-search` is a general-purpose semantic search across the entire knowledge graph — it finds nodes by vector similarity, which means results are ranked by semantic closeness to the query, not by domain relevance. A query containing the word "email" will surface product documentation *about* email features before it surfaces actual Email nodes whose content is unrelated to the query wording.
@@ -22,3 +22,11 @@ Images may be available at `/brand/` — these are visual assets the business ha
22
22
  - You answer ONLY from the knowledge provided to you. If the answer is not in your knowledge, say you don't know — do not use general training data or improvise facts about the business.
23
23
  - Stay within your explicit scope. Decline requests outside it politely.
24
24
  - If a user uploads a file, acknowledge that it has been saved for review. You cannot read, analyse, or act on file contents.
25
+
26
+ ## Questions
27
+
28
+ When you need to clarify intent before acting, follow two rules.
29
+
30
+ 1. **One-sided questions only.** Frame every clarifying question so a single-word "yes" or "no" is unambiguous. Never pose two opposing framings joined by "or" — "Should I book this, or not?", "Want a quote, or shall I take a message?". "Yes" to such a question is unusable — the visitor could have meant either side. Pick one side and ask it plainly: "Shall I take a message?" — not "take a message, or leave it?".
31
+
32
+ 2. **No menu when the signal is clear.** When an internal lookup or tool returns a specific outcome — the answer is there, or the answer is not — relay that outcome. Do not offer the visitor a menu of next steps when only one makes sense. If a lookup fails, tell the visitor what to do next (take a message, contact the business directly) rather than asking them to choose between recoveries.
@@ -3,7 +3,7 @@ name: personal-assistant
3
3
  description: "Your personal assistant — scheduling, platform administration, messaging channels, system health, and browser automation. Delegate when a task involves managing your calendar, configuring the platform, operating messaging channels, or completing interactive browser tasks."
4
4
  summary: "Handles the operational tasks you'd give a personal assistant — scheduling meetings, managing your platform settings, connecting messaging channels, and completing browser-based tasks on your behalf. For example, when you want to schedule a weekly check-in, set up Telegram, or fill out an online form."
5
5
  model: claude-sonnet-4-6
6
- tools: mcp__admin__system-status, mcp__admin__brand-settings, mcp__admin__account-manage, mcp__admin__account-update, mcp__admin__logs-read, mcp__admin__plugin-read, mcp__admin__api-key-store, mcp__admin__api-key-verify, mcp__admin__render-component, mcp__admin__file-attach, mcp__admin__wifi, mcp__contacts__contact-create, mcp__contacts__contact-lookup, mcp__contacts__contact-update, mcp__contacts__contact-delete, mcp__contacts__contact-list, mcp__contacts__contact-export, mcp__contacts__contact-erase, mcp__contacts__group-create, mcp__contacts__group-manage, mcp__cloudflare__cf-add-zone, mcp__cloudflare__cf-zone-status, mcp__cloudflare__cf-verify, mcp__cloudflare__cf-rebuild, mcp__cloudflare__tunnel-status, mcp__cloudflare__tunnel-install, mcp__cloudflare__tunnel-login, mcp__cloudflare__tunnel-create, mcp__cloudflare__tunnel-enable, mcp__cloudflare__tunnel-disable, mcp__cloudflare__tunnel-add-hostname, mcp__cloudflare__dns-lookup, mcp__telegram__message, mcp__telegram__message-history, mcp__telegram__telegram-webhook-register, mcp__whatsapp__whatsapp-login-start, mcp__whatsapp__whatsapp-login-wait, mcp__whatsapp__whatsapp-status, mcp__whatsapp__whatsapp-disconnect, mcp__whatsapp__whatsapp-send, mcp__whatsapp__whatsapp-send-document, mcp__whatsapp__whatsapp-config, mcp__whatsapp__whatsapp-activity, mcp__whatsapp__whatsapp-conversations, mcp__whatsapp__whatsapp-messages, mcp__whatsapp__whatsapp-group-info, mcp__email__email-setup, mcp__email__email-read, mcp__email__email-send, mcp__email__email-reply, mcp__email__email-search, mcp__email__email-graph-query, mcp__email__email-otp-extract, mcp__email__email-status, mcp__email__email-auto-respond-config, mcp__scheduling__schedule-event, mcp__scheduling__schedule-list, mcp__scheduling__schedule-get, mcp__scheduling__schedule-update, mcp__scheduling__schedule-cancel, mcp__scheduling__schedule-export-ics, mcp__scheduling__schedule-import-ics, mcp__scheduling__time-resolve, mcp__memory__memory-search, mcp__memory__profile-update, mcp__plugin_playwright_playwright__browser_navigate, mcp__plugin_playwright_playwright__browser_navigate_back, mcp__plugin_playwright_playwright__browser_snapshot, mcp__plugin_playwright_playwright__browser_click, mcp__plugin_playwright_playwright__browser_fill, mcp__plugin_playwright_playwright__browser_fill_form, mcp__plugin_playwright_playwright__browser_type, mcp__plugin_playwright_playwright__browser_press_key, mcp__plugin_playwright_playwright__browser_hover, mcp__plugin_playwright_playwright__browser_select_option, mcp__plugin_playwright_playwright__browser_wait_for, mcp__plugin_playwright_playwright__browser_handle_dialog, mcp__plugin_playwright_playwright__browser_evaluate, mcp__plugin_playwright_playwright__browser_console_messages, mcp__plugin_playwright_playwright__browser_resize, mcp__plugin_playwright_playwright__browser_tabs, mcp__plugin_playwright_playwright__browser_close
6
+ tools: mcp__admin__system-status, mcp__admin__brand-settings, mcp__admin__account-manage, mcp__admin__account-update, mcp__admin__logs-read, mcp__admin__plugin-read, mcp__admin__api-key-store, mcp__admin__api-key-verify, mcp__admin__render-component, mcp__admin__file-attach, mcp__admin__wifi, mcp__contacts__contact-create, mcp__contacts__contact-lookup, mcp__contacts__contact-update, mcp__contacts__contact-delete, mcp__contacts__contact-list, mcp__contacts__contact-export, mcp__contacts__contact-erase, mcp__contacts__group-create, mcp__contacts__group-manage, mcp__cloudflare__tunnel-status, mcp__cloudflare__tunnel-install, mcp__cloudflare__tunnel-login, mcp__cloudflare__tunnel-create, mcp__cloudflare__tunnel-enable, mcp__cloudflare__tunnel-disable, mcp__cloudflare__tunnel-add-hostname, mcp__cloudflare__dns-lookup, mcp__telegram__message, mcp__telegram__message-history, mcp__telegram__telegram-webhook-register, mcp__whatsapp__whatsapp-login-start, mcp__whatsapp__whatsapp-login-wait, mcp__whatsapp__whatsapp-status, mcp__whatsapp__whatsapp-disconnect, mcp__whatsapp__whatsapp-send, mcp__whatsapp__whatsapp-send-document, mcp__whatsapp__whatsapp-config, mcp__whatsapp__whatsapp-activity, mcp__whatsapp__whatsapp-conversations, mcp__whatsapp__whatsapp-messages, mcp__whatsapp__whatsapp-group-info, mcp__email__email-setup, mcp__email__email-read, mcp__email__email-send, mcp__email__email-reply, mcp__email__email-search, mcp__email__email-graph-query, mcp__email__email-otp-extract, mcp__email__email-status, mcp__email__email-auto-respond-config, mcp__scheduling__schedule-event, mcp__scheduling__schedule-list, mcp__scheduling__schedule-get, mcp__scheduling__schedule-update, mcp__scheduling__schedule-cancel, mcp__scheduling__schedule-export-ics, mcp__scheduling__schedule-import-ics, mcp__scheduling__time-resolve, mcp__memory__memory-search, mcp__memory__profile-update, mcp__plugin_playwright_playwright__browser_navigate, mcp__plugin_playwright_playwright__browser_navigate_back, mcp__plugin_playwright_playwright__browser_snapshot, mcp__plugin_playwright_playwright__browser_click, mcp__plugin_playwright_playwright__browser_fill, mcp__plugin_playwright_playwright__browser_fill_form, mcp__plugin_playwright_playwright__browser_type, mcp__plugin_playwright_playwright__browser_press_key, mcp__plugin_playwright_playwright__browser_hover, mcp__plugin_playwright_playwright__browser_select_option, mcp__plugin_playwright_playwright__browser_wait_for, mcp__plugin_playwright_playwright__browser_handle_dialog, mcp__plugin_playwright_playwright__browser_evaluate, mcp__plugin_playwright_playwright__browser_console_messages, mcp__plugin_playwright_playwright__browser_resize, mcp__plugin_playwright_playwright__browser_tabs, mcp__plugin_playwright_playwright__browser_close
7
7
  ---
8
8
 
9
9
  # Personal Assistant
@@ -41,21 +41,21 @@ Manages events, appointments, and recurring triggers in the graph.
41
41
 
42
42
  ## Cloudflare Tunnel
43
43
 
44
- Guides setting up a Cloudflare Tunnel so the platform is reachable via a custom domain. The bound Cloudflare account — whatever account the cert was issued for — is the entire universe of routable zones. The agent has absolute authority over that account: add zones, delete zones, create or delete tunnels, write or remove CNAMEs. Pollution is anything on the account that does not match the user's current intent. Authentication is OAuth-only via `tunnel-login`; on first success the device records an account binding (`account-binding.json`) and refuses subsequent operations if the cert is later rotated under a different Cloudflare account.
44
+ Guides setting up a Cloudflare Tunnel so the platform is reachable via a custom domain. The operator's logged-in Cloudflare dashboard is the single source of truth which domains exist, which account owns them, and which addresses are in use. The agent never reads or mutates account state via any API path. Sign-in is OAuth-only via `tunnel-login`; the laptop records which Cloudflare account that sign-in landed on so a later account switch is detected and surfaced.
45
45
 
46
- **Auth path:** `tunnel-login` (one-click OAuth in VNC browser). Pass `force=true` to clear cert + binding when switching Cloudflare accounts. There is no API-token auth path the plugin recognises only the cert-bound account identity.
46
+ **Auth path:** `tunnel-login` (one-click OAuth in VNC browser). Pass `force=true` to sign this laptop out (clears the local sign-in file) so a fresh sign-in can pick a different Cloudflare account. There is no token-based path.
47
47
 
48
- **Setup flow:** `cloudflare-setup` is the single orchestrator. It inspects the bound account, asks the user to pick a zone, surfaces any pollution on the account (other zones, other tunnels, stale CNAMEs pointing to dead tunnels), requires explicit confirmation before deleting, and then creates the tunnel with chosen admin/public subdomains. Every decision comes from a rendered UI component the agent is a courier for submissions, never synthesising values from free text.
48
+ **Setup flow:** Run `tunnel-login`, then `tunnel-create` with the domain and sub-address choices. The tools enforce pre-flight (does the domain's registrable parent have Cloudflare nameservers?) and post-flight (did `cloudflared` route the address under the requested name?) safety checks. A refusal always surfaces a dashboard instruction — never an API retry.
49
49
 
50
- **Diagnostic flow:** `cf-verify` returns the bound account's zones, tunnels, and CNAMEs alongside the device's cert / binding / tunnel state. It does not tag in-scope or out-of-scopethe agent decides what is pollution from conversation context. Non-mutating; safe to run in any state, including before login.
50
+ **Diagnostic flow:** `tunnel-status` does an end-to-end probe per configured address (DNS + HTTPS via Cloudflare's edge). `healthy: true` requires every address to probe `ok`. `boundAccountOwnsHostnames: false` means the tunnel is running on this laptop but nothing from the internet is reaching it almost always because the laptop is signed into a Cloudflare account that does not own the target domain.
51
51
 
52
- **Recovery flow:** `cf-rebuild` nukes everything on the bound account that is not in the caller's explicit `preserve` set. When called with no `preserve` AND no device-persisted intent (no `tunnel.state`, no `alias-domains.json`), it refuses with `reason=no-intent` rather than guess. Stale CNAMEs pointing to `<deletedTunnelId>.cfargotunnel.com` on otherwise-preserved zones are always scrubbed. Pass `dryRun=true` to preview.
52
+ **Recovery flow:** The single recovery path for every wrong-account failure is: operator opens Cloudflare in their browser, confirms the account name in the top-left is the one that owns the target domain (or switches accounts using the dropdown), confirms to the agent, then the agent runs `tunnel-login force=true` to re-authenticate. Any stray records `cloudflared` created under the wrong domain are cleaned up by the operator in the wrong-account dashboard the agent cannot reach account state.
53
53
 
54
- **DNS lookups:** `dns-lookup` for hostname resolution. Nameserver-not-yet-propagated is the most common operator issue; `cf-zone-status` reports activation status.
54
+ **DNS lookups:** `dns-lookup` for hostname resolution. Nameserver-not-yet-propagated is the most common operator issue `tunnel-status`'s e2e probe names the failure mode directly (`dns-missing`, `cname-points-elsewhere`, `edge-unreachable`, `tunnel-not-matched`).
55
55
 
56
- **Alias domains:** `tunnel-add-hostname` routes an additional hostname to an existing tunnel. The hostname must be a zone on the bound account; if it isn't, run `cf-add-zone <domain>` first.
56
+ **Alias addresses:** `tunnel-add-hostname` adds an additional address to an existing tunnel. The alias's domain must be on the same Cloudflare account this laptop is signed into — if not, the tool refuses with dashboard guidance.
57
57
 
58
- **User-facing language:** Say "connection" not "certificate", "domain" not "zone", "address" not "hostname". No Cloudflare internal terminology.
58
+ **User-facing language:** Say "Cloudflare account", "domain", "address", "sign in", "browser". Never say "zone", "CNAME", "account ID", "API", "SDK", or any hexadecimal identifier.
59
59
 
60
60
  **Verification:** Always verify URLs work by navigating to them in the browser. Never claim a URL works without browser verification.
61
61
 
@@ -7684,11 +7684,6 @@ var ADMIN_CORE_TOOLS = [
7684
7684
  "mcp__admin__action-approve",
7685
7685
  "mcp__admin__action-reject",
7686
7686
  "mcp__admin__action-edit",
7687
- "mcp__cloudflare__cloudflare-setup",
7688
- "mcp__cloudflare__cf-add-zone",
7689
- "mcp__cloudflare__cf-zone-status",
7690
- "mcp__cloudflare__cf-verify",
7691
- "mcp__cloudflare__cf-rebuild",
7692
7687
  "mcp__cloudflare__tunnel-status",
7693
7688
  "mcp__cloudflare__tunnel-install",
7694
7689
  "mcp__cloudflare__tunnel-login",
@@ -10395,6 +10390,7 @@ var VALID_SOURCES = /* @__PURE__ */ new Set([
10395
10390
  "session",
10396
10391
  "public",
10397
10392
  "mcp",
10393
+ "cloudflared",
10398
10394
  "config-dir"
10399
10395
  ]);
10400
10396
  var VALID_SCOPES = /* @__PURE__ */ new Set(["global", "session"]);
@@ -10548,8 +10544,9 @@ function defaultRules() {
10548
10544
  // Task 533: surface every Cloudflare-plugin refusal. The plugin emits
10549
10545
  // exactly one [cloudflare:refuse] line per refusal with a structured
10550
10546
  // reason field; any single occurrence on a previously-clean device
10551
- // means scope, account-binding, or post-flight FQDN drift the
10552
- // operator should run cf-verify before acting.
10547
+ // means the bound Cloudflare account does not match the operator's
10548
+ // intent (or the post-flight FQDN drifted) and the operator needs to
10549
+ // act in the dashboard.
10553
10550
  id: "cloudflare-refuse",
10554
10551
  name: "Cloudflare plugin refusal",
10555
10552
  type: "silent-catch",
@@ -10557,7 +10554,81 @@ function defaultRules() {
10557
10554
  pattern: "\\[cloudflare:refuse\\]|\\[cloudflare:post-flight-mismatch\\]",
10558
10555
  thresholdCount: 0,
10559
10556
  thresholdWindowMinutes: 0,
10560
- suggestedAction: "The Cloudflare plugin refused an operation or detected a post-flight FQDN mismatch. Run `cf-verify` to inspect current device state. If the reason is `account-drift` or `unbound-device`, run `tunnel-login force=true` to clear cert + binding and re-authenticate. If the reason is `scope-mismatch`, the requested hostname is outside the brand's declared zones \u2014 fix at brand.json + republish. If `post-flight-fqdn-mismatch`, run `cf-rebuild` to reconcile."
10557
+ suggestedAction: "The Cloudflare plugin refused an operation. Read the refusal `reason` field in the adjacent log line. For `account-drift` or `unbound-device`, run `tunnel-login force=true` while the operator is signed into the correct Cloudflare account in the browser. For `hostname-zone-not-routable`, the domain is not on Cloudflare yet \u2014 guide the operator to add it via the Cloudflare dashboard. For `post-flight-fqdn-mismatch` or `bound-account-does-not-own-hostname`, the laptop is signed into the wrong Cloudflare account \u2014 guide the operator to switch accounts in the dashboard, then re-run `tunnel-login force=true`."
10558
+ },
10559
+ {
10560
+ // Task 540: the single highest-priority refusal — surface it immediately
10561
+ // and independently of the generic cloudflare-refuse rule so the admin
10562
+ // agent sees it on the very next turn. This is the exact class that
10563
+ // burned the operator for 8 days across 9+ sessions (Apr 11–18, 2026):
10564
+ // tunnel running locally, dashboard serving the wrong account, nothing
10565
+ // from the internet reaches the laptop, and no prior telemetry surfaced
10566
+ // it in time for the agent to self-correct.
10567
+ id: "cloudflare-bound-account-mismatch",
10568
+ name: "Cloudflare bound account does not own the configured hostnames",
10569
+ type: "silent-catch",
10570
+ logSource: "any",
10571
+ pattern: '"reason":"bound-account-does-not-own-hostname"',
10572
+ thresholdCount: 0,
10573
+ thresholdWindowMinutes: 0,
10574
+ suggestedAction: "This laptop is signed into a Cloudflare account that does not own the hostnames the tunnel is configured to serve. Run `tunnel-status` to confirm, then tell the operator verbatim: 'The tunnel is running on this laptop but nothing from the internet is reaching it. The Cloudflare account this laptop is signed into doesn't own your domain. Open Cloudflare in your browser \u2014 is the account name in the top-left the one that owns your domain? If not, switch to the correct one, then tell me and I will re-sign-in.' When the operator confirms the correct account is selected, run `tunnel-login force=true`."
10575
+ },
10576
+ {
10577
+ // Task 541: tunnel-login's browser-launch failure class — cloudflared
10578
+ // reports "Failed to fetch resource" into cloudflared-login.log when
10579
+ // its browser launcher cannot reach the default browser, and the login
10580
+ // process lingers with a live PID holding the log open. Prior to Task
10581
+ // 541, `tunnel-login` inferred liveness from PID alone and kept
10582
+ // reporting "Sign-in in progress" against a dead auth flow. The MCP
10583
+ // handler now parses the log and emits this line on the first call
10584
+ // that detects the failure; the rule surfaces it to the admin agent on
10585
+ // the next turn so the 44-tool-call self-discovery loop (session
10586
+ // 0e30b69e) cannot reopen.
10587
+ id: "cloudflare-tunnel-login-failed",
10588
+ name: "Cloudflare tunnel-login failed (browser launcher fetch error)",
10589
+ type: "silent-catch",
10590
+ logSource: "any",
10591
+ pattern: "\\[cloudflare:tunnel-login:failed\\] reason=browser-launch-fetch-error",
10592
+ thresholdCount: 0,
10593
+ thresholdWindowMinutes: 0,
10594
+ suggestedAction: "cloudflared's browser launcher failed to open the sign-in URL on the laptop. The tunnel-login tool has already detected this and restarted the login \u2014 relay the new sign-in URL to the operator and wait for them to confirm authorization in the VNC browser. Do not open the Cloudflare dashboard in any other surface; the only path is the sign-in URL the tool produces."
10595
+ },
10596
+ {
10597
+ // Task 540: cloudflared.log is the one file most likely to carry the
10598
+ // "tunnel is having real-world connectivity problems" signal — QUIC
10599
+ // connection failures, connector drops, edge unreachability. Prior to
10600
+ // this rule it was written but never read (the review-detector had no
10601
+ // rule coverage for it). A single ERR line is worth surfacing; the
10602
+ // tee'd output is typically noise-free.
10603
+ id: "cloudflared-edge-errors",
10604
+ name: "cloudflared edge connectivity errors",
10605
+ type: "silent-catch",
10606
+ logSource: "cloudflared",
10607
+ pattern: "^\\S+ ERR (Failed to refresh protocol|no more connections active|Failed to dial a quic connection)",
10608
+ thresholdCount: 0,
10609
+ thresholdWindowMinutes: 0,
10610
+ suggestedAction: "cloudflared is reporting edge connectivity errors in its daemon log. Read the last 20 lines of `cloudflared.log` to see the surrounding context. Transient QUIC drops are normal; sustained failures (more than a handful in a minute) point to either a network issue on this laptop or an edge-side routing problem. Run `tunnel-status` to check whether end-to-end probing still succeeds."
10611
+ },
10612
+ {
10613
+ // Task 543: fires when an agent turn contains an opposing-axis choice-fork
10614
+ // question ("Want me to X, or Y?"). Every occurrence is a violation of the
10615
+ // IDENTITY § Questions rule — Rule A (one-sided questions) catches them
10616
+ // all, and the sharper sub-class (Rule B — no menu when a tool returned a
10617
+ // deterministic signal) is isolated by Task 544's `preceded-by` tightening.
10618
+ // logSource is "any" so the rule catches violations on both the admin
10619
+ // stream (claude-agent-stream-*) and the public agent stream
10620
+ // (public-agent-stream-*); the regex is agent-phrasing-specific and has
10621
+ // not been observed in other log contexts. Session scope groups matches
10622
+ // by conversationId so a single offending turn fires exactly once.
10623
+ id: "agent-choice-fork",
10624
+ name: "Agent emitted a choice-fork question",
10625
+ type: "repeated-error",
10626
+ logSource: "any",
10627
+ pattern: "Want me to [^\\n]+, or [^\\n]+\\?",
10628
+ thresholdCount: 1,
10629
+ thresholdWindowMinutes: 60,
10630
+ scope: "session",
10631
+ suggestedAction: 'Agent emitted a choice-fork question ("Want me to X, or Y?") instead of a one-sided question or the prescribed action. Review Task 543 IDENTITY \xA7 Questions \u2014 the agent asked an opposing-axis question, or degraded a deterministic tool signal into a menu. The log sample shows the offending turn verbatim.'
10561
10632
  }
10562
10633
  ];
10563
10634
  }
@@ -10786,6 +10857,10 @@ function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
10786
10857
  const p = resolve8(configDir2, "logs", "vnc-boot.log");
10787
10858
  return existsSync8(p) ? [{ logicalSource: "vnc", filepath: p }] : [];
10788
10859
  }
10860
+ if (logicalSource === "cloudflared") {
10861
+ const p = resolve8(configDir2, "logs", "cloudflared.log");
10862
+ return existsSync8(p) ? [{ logicalSource: "cloudflared", filepath: p }] : [];
10863
+ }
10789
10864
  const prefix = {
10790
10865
  system: "claude-agent-stream-",
10791
10866
  error: "claude-agent-stderr-",
@@ -10830,7 +10905,8 @@ function discoverAllSources(configDir2, accountLogDir2) {
10830
10905
  ...discoverSourceFiles(configDir2, accountLogDir2, "error"),
10831
10906
  ...discoverSourceFiles(configDir2, accountLogDir2, "session"),
10832
10907
  ...discoverSourceFiles(configDir2, accountLogDir2, "public"),
10833
- ...discoverSourceFiles(configDir2, accountLogDir2, "mcp")
10908
+ ...discoverSourceFiles(configDir2, accountLogDir2, "mcp"),
10909
+ ...discoverSourceFiles(configDir2, accountLogDir2, "cloudflared")
10834
10910
  ];
10835
10911
  }
10836
10912
  function readNewLines(filepath, prev) {