mercury-agent 0.5.2 → 0.5.6

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 (38) hide show
  1. package/package.json +1 -1
  2. package/docs/ARCHITECTURE.md +0 -34
  3. package/docs/DESIGN.md +0 -42
  4. package/docs/ROADMAP.md +0 -43
  5. package/docs/TODOS.md +0 -147
  6. package/docs/VISION.md +0 -32
  7. package/docs/archive/.gitkeep +0 -0
  8. package/docs/archive/applicative-profiles/2026-07-01-applicative-profiles.md +0 -270
  9. package/docs/archive/applicative-profiles/2026-07-01-caller-bound-capability-token.md +0 -184
  10. package/docs/archive/applicative-profiles/2026-07-01-dm-auto-space.md +0 -273
  11. package/docs/archive/summarization/2026-07-02-recent-dev-summary.md +0 -71
  12. package/docs/backlog/.gitkeep +0 -0
  13. package/docs/bugs/.gitkeep +0 -0
  14. package/docs/debug/major/.gitkeep +0 -0
  15. package/docs/debug/major/2026-07-01-strict-yaml-schema-crash.md +0 -85
  16. package/docs/debug/major/2026-07-02-bwrap-privileged-linux-docker.md +0 -160
  17. package/docs/debug/major/2026-07-02-e2big-prompt-delivery-fix.md +0 -382
  18. package/docs/debug/major/2026-07-02-registry-pull-no-local-fallback.md +0 -194
  19. package/docs/debug/minor/.gitkeep +0 -0
  20. package/docs/debug/moderate/.gitkeep +0 -0
  21. package/docs/debug/summarization/2026-07-02-common-bug-patterns.md +0 -72
  22. package/docs/html-slides/.gitkeep +0 -0
  23. package/docs/ideas/.gitkeep +0 -0
  24. package/docs/ideas/business-extensions.md +0 -48
  25. package/docs/in-progress/.gitkeep +0 -0
  26. package/docs/notes/.gitkeep +0 -0
  27. package/docs/pending-updates/.gitkeep +0 -0
  28. package/docs/runbooks/gws-oauth-setup.md +0 -211
  29. package/docs/runbooks/publish-checklist.md +0 -54
  30. package/docs/templates/TEMPLATE-AUTOPILOT-CONFIG.yaml +0 -35
  31. package/docs/templates/TEMPLATE-BUG.md +0 -71
  32. package/docs/templates/TEMPLATE-CI.yml +0 -25
  33. package/docs/templates/TEMPLATE-DECISIONS.md +0 -11
  34. package/docs/templates/TEMPLATE-FEATURE.md +0 -127
  35. package/docs/templates/TEMPLATE-GOAL.md +0 -31
  36. package/docs/templates/TEMPLATE-IDEA.md +0 -31
  37. package/docs/templates/TEMPLATE-NOTE.md +0 -27
  38. package/docs/templates/TEMPLATE-ROADMAP.md +0 -21
@@ -1,194 +0,0 @@
1
- # Bug: mercury run fails when registry pull fails but local build exists
2
-
3
- **Status:** Fixed
4
- **Severity:** P1 — blocks users who build locally but have a registry image configured
5
- **Introduced in:** 0.4.22 (commit 75e8e71)
6
- **File:** `src/cli/mercury.ts`, `runAction()` (~line 187)
7
- **Last updated:** 2026-07-02
8
-
9
- ## Symptom
10
-
11
- ```
12
- $ mercury build # succeeds → mercury-agent:latest exists locally
13
- $ mercury run
14
- Image 'ghcr.io/avishai-tsabari/mercury-agent:latest' not found locally, pulling...
15
- Error response from daemon: unauthorized
16
-
17
- Error: Failed to pull 'ghcr.io/avishai-tsabari/mercury-agent:latest'.
18
- If this is a private registry, authenticate first:
19
- docker login ghcr.io
20
- ```
21
-
22
- The user has a working local image (`mercury-agent:latest`) from `mercury build`,
23
- but `mercury run` exits with an error because the configured registry image
24
- can't be pulled (private registry, no auth, offline, etc.).
25
-
26
- ## Root cause
27
-
28
- The 0.4.22 change replaced the local-fallback logic with auto-pull. The old
29
- code tried `mercury-agent:latest` as a fallback when the configured image
30
- wasn't found. The new code attempts `docker pull` and hard-exits on failure —
31
- it never checks whether a locally built image exists.
32
-
33
- ### Before (0.4.21)
34
-
35
- ```
36
- configured image not found?
37
- → try mercury-agent:latest locally
38
- → found? use it (with warning)
39
- → not found? error: pull or build
40
- ```
41
-
42
- ### After (0.4.22)
43
-
44
- ```
45
- configured image not found?
46
- → is registry image? attempt pull
47
- → pull fails? hard exit ← BUG: should fall back to local
48
- → not registry? error: run mercury build
49
- ```
50
-
51
- ## Fix
52
-
53
- After a failed pull of a registry image, check if `mercury-agent:latest`
54
- exists locally before giving up. If it does, use it with a warning.
55
-
56
- ### Implementation (lines ~187–213 of `src/cli/mercury.ts`)
57
-
58
- Replace the current block:
59
-
60
- ```typescript
61
- if (imageCheck.status !== 0) {
62
- const isRegistryImage = imageName.includes("/");
63
- if (isRegistryImage) {
64
- console.log(`Image '${imageName}' not found locally, pulling...`);
65
- const pull = spawnSync("docker", ["pull", imageName], {
66
- stdio: "inherit",
67
- });
68
- if (pull.signal) {
69
- process.exit(1);
70
- }
71
- if (pull.status !== 0) {
72
- const firstSegment = imageName.split("/")[0];
73
- const registry = firstSegment.includes(".")
74
- ? firstSegment
75
- : "docker.io";
76
- console.error(`\nError: Failed to pull '${imageName}'.`);
77
- console.error(
78
- "If this is a private registry, authenticate first:\n" +
79
- ` docker login ${registry}`,
80
- );
81
- process.exit(1);
82
- }
83
- } else {
84
- console.error(`Error: Container image '${imageName}' not found.`);
85
- console.error("Run 'mercury build' to build it.");
86
- process.exit(1);
87
- }
88
- }
89
- ```
90
-
91
- With:
92
-
93
- ```typescript
94
- if (imageCheck.status !== 0) {
95
- const isRegistryImage = imageName.includes("/");
96
- let resolved = false;
97
-
98
- if (isRegistryImage) {
99
- console.log(`Image '${imageName}' not found locally, pulling...`);
100
- const pull = spawnSync("docker", ["pull", imageName], {
101
- stdio: "inherit",
102
- });
103
- if (pull.signal) {
104
- process.exit(1);
105
- }
106
- if (pull.status === 0) {
107
- resolved = true;
108
- }
109
- }
110
-
111
- // Fallback: if the configured image isn't available (pull failed, not a
112
- // registry image, or never pulled), try the local build tag.
113
- if (!resolved) {
114
- const localTag = "mercury-agent:latest";
115
- if (imageName !== localTag) {
116
- const localCheck = spawnSync("docker", ["image", "inspect", localTag], {
117
- stdio: "pipe",
118
- });
119
- if (localCheck.status === 0) {
120
- console.log(
121
- `\nℹ️ Using locally built ${localTag} (configured image unavailable)\n`,
122
- );
123
- process.env.MERCURY_AGENT_IMAGE = localTag;
124
- resolved = true;
125
- }
126
- }
127
- }
128
-
129
- if (!resolved) {
130
- if (isRegistryImage) {
131
- const firstSegment = imageName.split("/")[0];
132
- const registry = firstSegment.includes(".")
133
- ? firstSegment
134
- : "docker.io";
135
- console.error(`\nError: Failed to pull '${imageName}'.`);
136
- console.error(
137
- "If this is a private registry, authenticate first:\n" +
138
- ` docker login ${registry}\n` +
139
- "Or build locally:\n" +
140
- " mercury build",
141
- );
142
- } else {
143
- console.error(`Error: Container image '${imageName}' not found.`);
144
- console.error("Run 'mercury build' to build it.");
145
- }
146
- process.exit(1);
147
- }
148
- }
149
- ```
150
-
151
- ### Behavior after fix
152
-
153
- ```
154
- configured image not found?
155
- → is registry image? attempt pull
156
- → pull succeeds? use it ✓
157
- → pull fails? continue to fallback
158
- → try mercury-agent:latest locally
159
- → found? use it (with info message) ✓
160
- → not found? error with both suggestions (login + build) ✓
161
- ```
162
-
163
- ## How to verify
164
-
165
- 1. Build locally: `mercury build`
166
- 2. Ensure no GHCR auth: `docker logout ghcr.io`
167
- 3. Set `.env` to `MERCURY_AGENT_IMAGE=ghcr.io/avishai-tsabari/mercury-agent:latest`
168
- 4. Run `mercury run` — should fall back to local image with info message
169
- 5. Remove local image: `docker rmi mercury-agent:latest`
170
- 6. Run `mercury run` — should error with both `docker login` and `mercury build` suggestions
171
-
172
- ---
173
-
174
- ## Post-Mortem
175
-
176
- ### Investigation
177
- Triaged from a hanging bug doc during `/f-doc-sync`. First confirmed the fix was NOT already present: grepped `src/` for any local-fallback logic (`Using locally built`, `localTag`, `resolved`) — zero hits — and read `runAction()` in `src/cli/mercury.ts` (lines 186–216), which was still the buggy 0.4.22 version that hard-exits on pull failure. Fixed in an isolated worktree.
178
-
179
- ### Root Cause
180
- The 0.4.22 auto-pull rewrite dropped the pre-existing local-image fallback. When the configured image was a registry ref and `docker pull` failed (private registry / no auth / offline), `runAction()` called `process.exit(1)` immediately, never checking whether a locally built `mercury-agent:latest` existed.
181
-
182
- ### Fix
183
- `src/cli/mercury.ts`, `runAction()` (committed to main as `1c120bf`):
184
- - Introduced a `resolved` flag. A registry pull sets it on success; on failure, control falls through instead of exiting.
185
- - Added a fallback block: if unresolved and the configured image isn't already `mercury-agent:latest`, `docker image inspect mercury-agent:latest`; if present, use it with an info message.
186
- - Only after both paths fail does it `process.exit(1)`, now suggesting **both** `docker login` and `mercury build`.
187
- - **Beyond the original fix guide:** the child is spawned with `env: { ...process.env, ...envVars }`, and `envVars` (loaded from `.env`, which typically pins `MERCURY_AGENT_IMAGE` to the registry) wins that merge. Setting only `process.env.MERCURY_AGENT_IMAGE` would have been silently clobbered — the repro scenario itself. The fix therefore also sets `envVars.MERCURY_AGENT_IMAGE = localTag`. Confirmed load-bearing by the session code review.
188
-
189
- Verification: `bun run typecheck` + `bun run lint` clean; `bun test` → 1039 pass / 0 fail; session code review returned "No issues found."
190
-
191
- ### Lessons
192
- - When a spawn merges multiple env sources (`{ ...process.env, ...envVars }`), an override must be written to the **last-wins** source, not just `process.env` — otherwise it's silently reverted.
193
- - Behavior-replacing rewrites (the 0.4.22 auto-pull change) should preserve existing fallbacks explicitly; a regression test for "pull fails but local image exists" would have caught this.
194
- - Reconcile hanging bug docs against current code before opening a fix — two sibling P0/P1 docs (`e2big`, `bwrap-privileged`) turned out already fixed; only this one needed work.
File without changes
File without changes
@@ -1,72 +0,0 @@
1
- # Common Bug Patterns
2
-
3
- **Date:** 2026-07-02
4
- **Covers:** 1 bug report (1 major, 0 moderate, 0 minor) · 2026-07-01 → 2026-07-01
5
-
6
- This is the first bug-pattern summary. It covers a single major incident — the strict-YAML-schema crash — but that one bug braids together several distinct, reusable failure patterns worth capturing before they recur: over-strict input validation, unbounded restart loops, and the fragile-transport amplification that turns any crash into extended downtime. The dominant theme is **conflation**: treating "unknown input" and "invalid input" as the same failure, and treating "one crash" as harmless when the surrounding system amplifies it.
7
-
8
- ## Findings
9
-
10
- ### 1. Over-strict input validation (unknown key ≠ invalid value)
11
-
12
- Schemas that reject unrecognized input with the same severity as malformed input turn benign drift (typos, renamed fields, version skew) into hard failures.
13
-
14
- | Bug | What went wrong |
15
- |-----|-----------------|
16
- | strict-yaml-schema-crash | `mercuryFileSchema` used Zod `.strict()` on all 13 nested objects, so a single unknown `mercury.yaml` key (e.g. renamed `admin_numbers` → `admin_ids`) crashed startup with the same severity as an invalid value. |
17
-
18
- **Rule:** Separate "unknown key" (warn, strip, continue) from "invalid value" (fail fast). Default config schemas to strip-and-warn on unrecognized keys; reserve hard failure for values that are actually wrong.
19
-
20
- ### 2. Version-skew intolerance
21
-
22
- Config that must be exactly in sync with the running binary breaks on every field rename, punishing the normal lifecycle of upgrades and rollbacks.
23
-
24
- | Bug | What went wrong |
25
- |-----|-----------------|
26
- | strict-yaml-schema-crash | A field renamed between 0.4.26 and 0.4.27 (`admin_numbers` → `admin_ids`) crashed any deployment whose yaml hadn't been updated in lockstep. |
27
-
28
- **Rule:** Any feature that adds or renames a config field must assume a user on version N may run a yaml from version N±1. Forward/backward-unknown keys should degrade gracefully, not crash.
29
-
30
- ### 3. Unbounded restart loops amplify a single failure
31
-
32
- A deterministic startup error plus an unbounded auto-restarter converts one crash into an infinite crash loop, removing any window for human intervention.
33
-
34
- | Bug | What went wrong |
35
- |-----|-----------------|
36
- | strict-yaml-schema-crash | PM2's default unlimited restarts retried the identical failing config forever, each attempt failing the same way. |
37
-
38
- **Rule:** Configure `max_restarts` + `min_uptime` on process supervisors so a deterministic failure stops after N attempts and alerts, rather than looping. A restart policy is not a substitute for handling the error.
39
-
40
- ### 4. Fragile-transport amplification
41
-
42
- When a downstream dependency punishes rapid reconnection, an unrelated crash loop escalates into a much costlier outage than the original bug.
43
-
44
- | Bug | What went wrong |
45
- |-----|-----------------|
46
- | strict-yaml-schema-crash | The crash loop drove rapid WhatsApp/Baileys reconnects, which Meta treats as abuse → session revoked (reason=401) → manual QR re-scan required for recovery. |
47
-
48
- **Rule:** Treat any crash loop as catastrophic when a session-based/unofficial transport is in play. Isolate transport-session lifecycle from process restarts, and prefer a circuit breaker that keeps the host alive while surfacing the error.
49
-
50
- ## Summary table
51
-
52
- | Pattern | Count | Worst severity |
53
- |---------|-------|----------------|
54
- | Over-strict input validation | 1 | major |
55
- | Version-skew intolerance | 1 | major |
56
- | Unbounded restart loops | 1 | major |
57
- | Fragile-transport amplification | 1 | major |
58
-
59
- ## Comparison with previous summary
60
-
61
- None — this is the first bug-pattern summary. Future summaries should track whether validation-strictness and restart-loop patterns recur once the fixes above land.
62
-
63
- ## Decisions
64
-
65
- 1. **Adopt strip-and-warn as the config default** across all schemas; audit remaining `.strict()` usages for the same class of crash.
66
- 2. **Set supervisor restart limits** (`max_restarts`, `min_uptime`) plus an alert on limit-hit, so no deterministic failure can loop indefinitely.
67
- 3. **Design a container/transport circuit breaker** (the bug's own Phase 2) so container-level errors log-and-recover instead of crashing the host and endangering the WhatsApp session.
68
-
69
- ## Open Questions
70
-
71
- - Should config validation ever hard-fail on unknown keys in a "strict mode" opt-in for CI, while defaulting to warn in production?
72
- - What is the right `max_restarts` threshold given Baileys' revocation sensitivity — and should transport reconnection back off independently of process restarts?
File without changes
File without changes
@@ -1,48 +0,0 @@
1
- ---
2
- name: business-extensions
3
- description: Dedicated business extensions that wrap raw capabilities (GWS, etc.) with deterministic domain logic — e.g. a barber-appointments extension
4
- metadata:
5
- type: idea
6
- ---
7
-
8
- # Business Extensions
9
-
10
- **Status**: Idea
11
- **Created**: 2026-07-01
12
-
13
- ## Problem
14
-
15
- Raw capability extensions (GWS Calendar, Gmail, web search) expose generic APIs. Business workflows (appointment booking, customer management) require deterministic logic — availability checks, double-booking prevention, confirmation flows — that can't be reliably delegated to the LLM via prompting alone.
16
-
17
- Today the only options are: (1) let the LLM orchestrate raw APIs via system prompt instructions (not deterministic enough), or (2) build a full standalone application outside Mercury. Neither is ideal.
18
-
19
- ## Idea
20
-
21
- A pattern for **business extensions** that wrap raw capabilities with domain-specific, deterministic logic and expose simplified CLIs to the agent.
22
-
23
- Example: `barber-appointments` extension
24
- - Wraps GWS Calendar internally
25
- - Exposes: `book-appointment`, `list-availability`, `cancel-appointment`
26
- - Enforces business rules in code: no double-booking, business hours only, minimum slot duration, confirmation required
27
- - Registers as a single Mercury permission (`barber-appointments`)
28
- - The LLM calls the simplified CLI; the extension handles the hard logic deterministically
29
-
30
- This separates concerns:
31
- - **Mercury** = platform (messaging, spaces, permissions, rate limits)
32
- - **Capability extensions** = raw API access (GWS, web search, etc.) — admin-only or building blocks
33
- - **Business extensions** = deterministic domain logic wrapping capabilities — customer-facing
34
-
35
- ## Why not just prompt engineering?
36
-
37
- LLMs are non-deterministic. For a barber shop, a double-booked appointment or a cancelled-without-confirmation is a real-world problem. Business rules must be enforced in code, not hoped for via prompts.
38
-
39
- ## Open questions
40
-
41
- - Should business extensions be a separate category in the extension system, or just a convention?
42
- - How do business extensions declare their dependency on capability extensions (e.g., `barber-appointments` needs `gws`)?
43
- - Should there be a template/scaffold for creating business extensions?
44
- - How does this relate to "profiles" — is a profile = business extension + config preset?
45
-
46
- ## First candidate
47
-
48
- `barber-appointments` — Calendar-based appointment booking for a barber shop. Quick win for first customer impression. Gmail integration deferred.
File without changes
File without changes
File without changes
@@ -1,211 +0,0 @@
1
- # GWS OAuth Setup Runbook
2
-
3
- How to connect Google Calendar to a customer's Mercury assistant.
4
-
5
- ## Prerequisites
6
-
7
- - Access to the Tagula GCP project (console.cloud.google.com, project "Tagula")
8
- - SSH access to the customer's VPS
9
- - The customer's `MERCURY_API_SECRET` (from their `.env`)
10
- - The customer's Google account email
11
-
12
- ## Option A: Auth Gateway (preferred — works on mobile + desktop)
13
-
14
- Use this when `auth.tagula.ai` is set up. Customer just clicks a link.
15
-
16
- ### One-time gateway setup
17
-
18
- > Skip if the gateway is already running at `auth.tagula.ai`.
19
-
20
- 1. **DNS**: Add an A record for `auth.tagula.ai` → gateway server IP (GoDaddy DNS panel)
21
-
22
- 2. **Server**: SSH into the gateway server and install nginx + certbot:
23
- ```bash
24
- apt-get update && apt-get install -y nginx certbot python3-certbot-nginx
25
- ```
26
-
27
- 3. **Nginx config** (`/etc/nginx/sites-available/auth-tagula`):
28
- ```nginx
29
- server {
30
- listen 80;
31
- server_name auth.tagula.ai;
32
- location / {
33
- proxy_pass http://127.0.0.1:3456;
34
- proxy_set_header Host $host;
35
- proxy_set_header X-Forwarded-For $remote_addr;
36
- }
37
- }
38
- ```
39
- ```bash
40
- ln -s /etc/nginx/sites-available/auth-tagula /etc/nginx/sites-enabled/
41
- nginx -t && systemctl reload nginx
42
- ```
43
-
44
- 4. **SSL**:
45
- ```bash
46
- certbot --nginx -d auth.tagula.ai
47
- ```
48
-
49
- 5. **Gateway config**: Copy `gateway-config.example.json` → `gateway-config.json`, fill in:
50
- - `google.clientId` and `google.clientSecret` from the Tagula GCP project
51
- - `google.redirectUri` = `https://auth.tagula.ai/oauth/callback`
52
-
53
- 6. **GCP redirect URI**: In GCP Console → APIs & Services → Credentials → Web client → add `https://auth.tagula.ai/oauth/callback` to Authorized redirect URIs
54
-
55
- 7. **Start gateway**:
56
- ```bash
57
- npm install -g pm2 # if not installed
58
- pm2 start auth-gateway.js --name auth-gateway
59
- pm2 save
60
- ```
61
-
62
- ### Per-customer setup
63
-
64
- 1. **Add customer to gateway config** (`gateway-config.json`):
65
- ```json
66
- "assistants": {
67
- "customer-name": {
68
- "apiUrl": "http://<VPS_IP>:8787",
69
- "apiSecret": "<MERCURY_API_SECRET from customer's .env>",
70
- "envVar": "MERCURY_GWS_CREDENTIALS_JSON"
71
- }
72
- }
73
- ```
74
- Restart gateway: `pm2 restart auth-gateway`
75
-
76
- 2. **Add test user in GCP**: GCP Console → Google Auth Platform → Audience → Add users → add customer's Gmail
77
-
78
- 3. **Install gws extension on customer VPS** (if not already):
79
- ```bash
80
- ssh root@<VPS_IP>
81
- # Via dashboard: go to http://<VPS_IP>:8787/dashboard/page/features → Install gws
82
- # Or via API:
83
- curl -X POST http://localhost:8787/api/console/extensions/install \
84
- -H "Authorization: Bearer <API_SECRET>" \
85
- -H "Content-Type: application/json" \
86
- -d '{"catalogName": "gws"}'
87
- ```
88
-
89
- 4. **Send the link to customer**:
90
- ```
91
- https://auth.tagula.ai/connect/gws?assistant=customer-name
92
- ```
93
- Customer clicks → signs in with Google → sees "Connected!" → done.
94
-
95
- 5. **Verify**: Check Mercury logs on the VPS:
96
- ```bash
97
- pm2 logs 'mercury run' --lines 20 --nostream | grep -i gws
98
- ```
99
- Should show: `Loaded extension: gws` and `Installed skill: gws`
100
-
101
- ---
102
-
103
- ## Option B: Manual (ngrok — no gateway needed)
104
-
105
- Use this before the gateway is set up, or as a one-off.
106
-
107
- ### Setup
108
-
109
- 1. **Install ngrok on customer VPS**:
110
- ```bash
111
- ssh root@<VPS_IP>
112
- curl -sSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
113
- | tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null
114
- echo 'deb https://ngrok-agent.s3.amazonaws.com buster main' \
115
- | tee /etc/apt/sources.list.d/ngrok.list
116
- apt-get update && apt-get install -y ngrok
117
- ngrok config add-authtoken <YOUR_NGROK_TOKEN>
118
- ```
119
-
120
- 2. **Write callback script** (`/tmp/oauth-callback.js`):
121
- ```bash
122
- # Copy from tagula-agents-cloud-console/oauth-callback-standalone.js
123
- # Update CLIENT_ID and CLIENT_SECRET from GCP
124
- scp oauth-callback-standalone.js root@<VPS_IP>:/tmp/oauth-callback.js
125
- ```
126
-
127
- 3. **Start callback + ngrok**:
128
- ```bash
129
- ssh root@<VPS_IP>
130
- nohup node /tmp/oauth-callback.js > /tmp/oauth-callback.log 2>&1 &
131
- ngrok http 9999
132
- ```
133
- Note the ngrok URL (e.g., `https://abc123.ngrok-free.dev`)
134
-
135
- 4. **Configure redirect URI**:
136
- - Set redirect URI in the callback script:
137
- ```bash
138
- curl "http://localhost:9999/set-redirect?uri=https://<NGROK_URL>/oauth/callback"
139
- ```
140
- - Add same URL to GCP: APIs & Services → Credentials → Web client → Authorized redirect URIs
141
-
142
- 5. **Add test user**: GCP → Google Auth Platform → Audience → Add customer's Gmail
143
-
144
- 6. **Send OAuth link to customer**:
145
- ```
146
- https://accounts.google.com/o/oauth2/v2/auth?client_id=<CLIENT_ID>&redirect_uri=https://<NGROK_URL>/oauth/callback&response_type=code&scope=https://www.googleapis.com/auth/calendar.events&access_type=offline&prompt=consent
147
- ```
148
-
149
- 7. **After customer authorizes**:
150
- - ngrok free tier shows an interstitial — customer clicks "Visit Site"
151
- - Check logs: `cat /tmp/oauth-callback.log`
152
- - Should show `SUCCESS - credentials saved`
153
-
154
- 8. **Move credentials to correct .env** (the callback script writes to `.mercury/.env` but Mercury reads from project root `.env`):
155
- ```bash
156
- grep MERCURY_GWS /opt/path/to/assistant/.mercury/.env >> /opt/path/to/assistant/.env
157
- sed -i '/MERCURY_GWS/d' /opt/path/to/assistant/.mercury/.env
158
- ```
159
-
160
- 9. **Restart Mercury and clean up**:
161
- ```bash
162
- pm2 restart 'mercury run'
163
- # Kill callback server and ngrok
164
- kill $(lsof -ti:9999) 2>/dev/null
165
- pkill ngrok
166
- rm /tmp/oauth-callback.js /tmp/oauth-callback.log
167
- ```
168
-
169
- 10. **Verify**:
170
- ```bash
171
- pm2 logs 'mercury run' --lines 20 --nostream | grep -i gws
172
- ```
173
-
174
- ---
175
-
176
- ## Option C: Copy-paste URL (desktop only)
177
-
178
- Simplest, no infrastructure needed. Customer must be on a computer (not mobile).
179
-
180
- 1. **Create Desktop-type OAuth client** in GCP (not Web) — allows `http://localhost` redirect
181
- 2. **Generate OAuth URL** with `redirect_uri=http://localhost:1`
182
- 3. **Send link to customer** — they click, authorize, Google redirects to localhost (fails)
183
- 4. **Customer copies the full URL** from their browser bar and sends it back
184
- 5. **Extract code and exchange**:
185
- ```bash
186
- # Extract code from the URL the customer sent
187
- CODE="the_code_from_url"
188
- curl -s -X POST https://oauth2.googleapis.com/token \
189
- -d "code=$CODE&client_id=<CLIENT_ID>&client_secret=<CLIENT_SECRET>&redirect_uri=http://localhost:1&grant_type=authorization_code"
190
- ```
191
- 6. **Set env var and restart** — copy the tokens JSON into `.env` as `MERCURY_GWS_CREDENTIALS_JSON='...'`
192
-
193
- ---
194
-
195
- ## Troubleshooting
196
-
197
- | Symptom | Cause | Fix |
198
- |---------|-------|-----|
199
- | `invalid_client` on Google consent | Client not propagated yet, or created in new Auth Platform UI | Wait 10 min, or recreate from legacy APIs & Services → Credentials |
200
- | Extension installs but doesn't load | `MERCURY_GWS_CREDENTIALS_JSON` not set or in wrong `.env` | Check `grep MERCURY_GWS .env` in project root |
201
- | `Removed stale skill: gws` in logs | Extension skipped by credential gate | Same as above — credential missing |
202
- | `Failed to build derived image` | `docker-buildx` not installed | `apt-get install docker-buildx` |
203
- | Customer's wrong Google account used | Browser auto-selects default account | Add all their accounts as test users, or use incognito |
204
- | ngrok "Visit Site" interstitial | Free tier warning | Customer clicks "Visit Site" — shows once |
205
- | `refresh_token_expires_in: 604799` | Token expires in 7 days (testing mode) | Publish the OAuth app in GCP to get non-expiring refresh tokens |
206
-
207
- ## Important notes
208
-
209
- - **Test mode tokens expire in 7 days.** While the GCP app is in "Testing" status, refresh tokens expire after 7 days. To get permanent tokens, publish the app (requires Google verification for sensitive scopes). For now, re-run the OAuth flow when tokens expire.
210
- - **docker-buildx** is required on Linux servers for Mercury to build derived images with extension CLIs. Install with `apt-get install docker-buildx`.
211
- - **Rotate credentials** after sharing them in chat. Go to GCP → Credentials → Web client → rotate client secret. Update `gateway-config.json` and any callback scripts.
@@ -1,54 +0,0 @@
1
- # Publish Checklist
2
-
3
- Before publishing a new version to npm:
4
-
5
- 1. **`bun run check`** — typecheck + lint + tests
6
- 2. **`mercury build`** — if you touched build path, Dockerfiles, or file-copy logic
7
- 3. **`mercury init` + `mercury service install`** in a test project — if you touched CLI flow, config loading, or service management
8
- 4. **Smoke test assistants** — send a message, verify they respond
9
-
10
- ## Version bump & publish
11
-
12
- ```bash
13
- npm version patch --no-git-tag-version # or minor/major
14
- git add package.json
15
- git commit -m "chore: bump version to $(node -p 'require("./package.json").version')"
16
- git tag "v$(node -p 'require("./package.json").version')"
17
- git push && git push --tags
18
-
19
- # Publish to npm manually. npm 2FA requires an interactive one-time
20
- # password, so this cannot run in CI — pass it inline (it expires in ~30s):
21
- npm publish --otp=<code-from-authenticator>
22
- ```
23
-
24
- Always tag and publish together — `package.json` is the single source of truth for both npm and GitHub.
25
-
26
- **CI does not publish to npm.** Pushing a `v*` tag runs `bun run check` and creates a
27
- GitHub Release (`.github/workflows/release.yml`), but the npm upload is a manual step
28
- done by a maintainer from a local machine. Verify afterward with
29
- `npm view mercury-agent version`.
30
-
31
- ## Beta testing on another machine
32
-
33
- Use `--tag beta` to publish a test version without affecting the stable `latest` tag.
34
-
35
- ```bash
36
- # 1. Bump to a beta prerelease
37
- npm version prerelease --preid=beta # e.g. 0.4.24 → 0.4.25-beta.0
38
-
39
- # 2. Publish under the beta tag (won't affect `npm install mercury-agent`)
40
- npm publish --tag beta
41
-
42
- # 3. On the other machine, install the beta
43
- npm install -g mercury-agent@beta
44
-
45
- # 4. Test, iterate. Bump again if needed:
46
- npm version prerelease --preid=beta # 0.4.25-beta.0 → 0.4.25-beta.1
47
- npm publish --tag beta
48
-
49
- # 5. When satisfied, promote to stable:
50
- npm version patch # 0.4.25-beta.1 → 0.4.25
51
- npm publish # publishes to "latest"
52
- ```
53
-
54
- `npm install -g mercury-agent` (without @beta) always gets the stable version. Only explicit `@beta` gets the test version.
@@ -1,35 +0,0 @@
1
- # Autopilot Configuration
2
- # Copy this file to docs/autopilot/config.yaml and customize.
3
- # The orchestrator script reads this file each iteration.
4
-
5
- gates:
6
- max_iterations: 10 # stop after N iterations per session
7
- max_features_without_review: 3 # stop after completing N features/bugs without human review
8
- max_retries_same_task: 3 # skip a task after N failed attempts
9
- iteration_timeout_minutes: 30 # kill a single execute phase after this long
10
-
11
- risk:
12
- sensitive_paths: # changes to these = immediate stop for review
13
- - "**/auth/**"
14
- - "**/migration*"
15
- - "**/.env*"
16
- - "**/schema*"
17
- auto_stop_risk_threshold: 8 # risk_score >= this from assess = stop
18
-
19
- scope:
20
- allowed_actions: # which actions the autopilot may take
21
- - implement_feature
22
- - continue_feature
23
- - fix_bug
24
- - sync_docs
25
- # - propose_decomposition # uncomment to enable autonomous planning (requires allow_autonomous_planning: true)
26
- skip_slugs: # slugs to never touch autonomously
27
- # - my-feature-slug
28
-
29
- planning:
30
- allow_autonomous_planning: false # when true, autopilot can decompose ideas into draft plans (requires propose_decomposition in allowed_actions)
31
- # plans land as Status: Proposed and always stop for human review
32
-
33
- notifications:
34
- on_stop: console # "console" | "file" (future: "slack", "email")
35
- decision_log: docs/autopilot/decision-log.md