mercury-agent 0.5.2 → 0.5.7
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/package.json +1 -1
- package/docs/ARCHITECTURE.md +0 -34
- package/docs/DESIGN.md +0 -42
- package/docs/ROADMAP.md +0 -43
- package/docs/TODOS.md +0 -147
- package/docs/VISION.md +0 -32
- package/docs/archive/.gitkeep +0 -0
- package/docs/archive/applicative-profiles/2026-07-01-applicative-profiles.md +0 -270
- package/docs/archive/applicative-profiles/2026-07-01-caller-bound-capability-token.md +0 -184
- package/docs/archive/applicative-profiles/2026-07-01-dm-auto-space.md +0 -273
- package/docs/archive/summarization/2026-07-02-recent-dev-summary.md +0 -71
- package/docs/backlog/.gitkeep +0 -0
- package/docs/bugs/.gitkeep +0 -0
- package/docs/debug/major/.gitkeep +0 -0
- package/docs/debug/major/2026-07-01-strict-yaml-schema-crash.md +0 -85
- package/docs/debug/major/2026-07-02-bwrap-privileged-linux-docker.md +0 -160
- package/docs/debug/major/2026-07-02-e2big-prompt-delivery-fix.md +0 -382
- package/docs/debug/major/2026-07-02-registry-pull-no-local-fallback.md +0 -194
- package/docs/debug/minor/.gitkeep +0 -0
- package/docs/debug/moderate/.gitkeep +0 -0
- package/docs/debug/summarization/2026-07-02-common-bug-patterns.md +0 -72
- package/docs/html-slides/.gitkeep +0 -0
- package/docs/ideas/.gitkeep +0 -0
- package/docs/ideas/business-extensions.md +0 -48
- package/docs/in-progress/.gitkeep +0 -0
- package/docs/notes/.gitkeep +0 -0
- package/docs/pending-updates/.gitkeep +0 -0
- package/docs/runbooks/gws-oauth-setup.md +0 -211
- package/docs/runbooks/publish-checklist.md +0 -54
- package/docs/templates/TEMPLATE-AUTOPILOT-CONFIG.yaml +0 -35
- package/docs/templates/TEMPLATE-BUG.md +0 -71
- package/docs/templates/TEMPLATE-CI.yml +0 -25
- package/docs/templates/TEMPLATE-DECISIONS.md +0 -11
- package/docs/templates/TEMPLATE-FEATURE.md +0 -127
- package/docs/templates/TEMPLATE-GOAL.md +0 -31
- package/docs/templates/TEMPLATE-IDEA.md +0 -31
- package/docs/templates/TEMPLATE-NOTE.md +0 -27
- 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
|
package/docs/ideas/.gitkeep
DELETED
|
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
|
package/docs/notes/.gitkeep
DELETED
|
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
|