Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ node_modules/
dist/
.env
*.log
log
.DS_Store
*.tsbuildinfo
config/
soul/
memory/
memory/
.hermes/
tmp/
2 changes: 2 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ allowed-tools:
Instructions for Mercury to follow when this skill is invoked...
```

`allowed-tools` make those tools available while the skill runs, but they do not bypass filesystem scopes or blocked shell commands.

### Progressive Disclosure

- **Startup**: Only skill names + descriptions are loaded (token-efficient)
Expand Down
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Mercury has been in rapid development through 0.x releases. The Second Brain fea
### Permission Modes

- **Ask Me** — confirm before file writes, shell commands that need approval, and scope changes (default on both CLI and Telegram)
- **Allow All** — auto-approve everything for the session (scopes, commands, loop continuation). Resets on restart.
- **Allow All** — auto-approve everything in the current session/channel while keeping the shell blocklist and filesystem scoping in force. Resets on restart.
- CLI: arrow-key menu at session start. Telegram: inline keyboard on first message, `/permissions` to change.

### Step-by-Step Tool Feedback
Expand All @@ -54,7 +54,7 @@ Mercury has been in rapid development through 0.x releases. The Second Brain fea
- **Model selection during onboarding** — after validating an API key, Mercury fetches available models and lets you choose
- **Telegram editable status messages** — streaming updates use `editMessageText` for live response editing
- **Scheduled task notifications** — Mercury notifies the originating channel when a scheduled task runs
- **Full temporary scope for scheduled tasks** — tasks run in Allow All mode with auto-approved scopes
- **Scheduled tasks follow the restricted auto-approval model** — tasks run with `Allow All` behavior inside their originating session/channel, without any extra root filesystem scope

### Breaking Changes

Expand Down
4 changes: 2 additions & 2 deletions DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
## ADR-008: Scheduler with YAML persistence

- **Context**: Mercury needs to set reminders, run periodic tasks, and trigger skills on a schedule.
- **Decision**: Expose `schedule_task`, `list_scheduled_tasks`, `cancel_scheduled_task` as AI-callable tools. Persist scheduled tasks to `~/.mercury/schedules.yaml`. Restore on startup. Tasks fire as internal (non-channel) messages through the agent loop.
- **Consequence**: Mercury can autonomously schedule work. Tasks survive restarts. Internal execution keeps scheduled tasks invisible to channels unless the agent explicitly sends output.
- **Decision**: Expose `schedule_task`, `list_scheduled_tasks`, `cancel_scheduled_task` as AI-callable tools. Persist scheduled tasks to `~/.mercury/schedules.yaml`. Restore on startup. Tasks run as system messages through the agent loop, preserving `sourceChannelId` / `sourceChannelType` when available instead of forcing a separate non-channel execution path.
- **Consequence**: Mercury can autonomously schedule work. Tasks survive restarts. Scheduled runs inherit channel context for delivery and session isolation when that context exists, while still using system-message auto-approval. This auto-approval does not grant root filesystem scope.

## ADR-009: Daemonization via Custom Hybrid Approach

Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ mercury doctor

Every AI agent can read files, run commands, and fetch URLs. Most do it silently. **Mercury asks first — and remembers what matters.**

- **Permission-hardened** — Shell blocklist (`sudo`, `rm -rf /`, etc. never execute). Folder-level read/write scoping. Pending approval flow. Ask Me or Allow All per session. No surprises.
- **Permission-hardened** — Shell blocklist (`sudo`, `rm -rf /`, etc. never execute). Folder-level read/write scoping. Pending approval flow. Ask Me or Allow All per session. `Allow All` auto-approves within the current session/channel, but does not grant root filesystem access.
- **Second Brain** — Persistent, structured memory with SQLite + FTS5 full-text search. 10 memory types, auto-extraction, conflict resolution, auto-consolidation. Mercury learns your preferences, goals, and habits without manual entry.
- **Soul-driven** — Personality defined by markdown files you own (`soul.md`, `persona.md`, `taste.md`, `heartbeat.md`). No corporate wrapper.
- **Token-aware** — Daily budget enforcement. Auto-concise when over 70%. `/budget` command to check, reset, or override.
Expand Down Expand Up @@ -153,6 +153,14 @@ Type these during a conversation — they don't consume API tokens. Work on both
| `/memory` | View and manage second brain memory |
| `/unpair` | Telegram: reset all access |

### Permission modes

- **Ask Me** — prompts before risky shell commands, writes, and permission escalations.
- **Allow All** — auto-approves within the current session/channel, but keeps the shell blocklist and filesystem scoping in force.
- Permission state is isolated by session/channel, so one Telegram chat or CLI session does not silently unlock another.
- System-triggered runs (internal flows and scheduled tasks) are auto-approved under the same restricted model — no extra root filesystem scope is added.
- See `docs/permissions-model.md` for the live permission model and implementation notes.

## Built-in Tools

| Category | Tools |
Expand Down
10 changes: 5 additions & 5 deletions docs/docs.html
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ <h2 id="in-chat">In-Chat Commands</h2>
</tr>
<tr>
<td>/permissions</td>
<td>Change permission mode (Ask Me / Allow All) — Telegram only</td>
<td>Change permission mode (Ask Me / Allow All) on CLI or Telegram</td>
</tr>
<tr>
<td>/tasks</td>
Expand Down Expand Up @@ -1379,17 +1379,17 @@ <h2 id="permissions">Permissions</h2>
permanently approve a command type</li>
</ol>
<p>Edit <code>~/.mercury/permissions.yaml</code> to customize. Skill elevation: skills with <code>allowed-tools
</code> in their SKILL.md get automatic approval for those tools during execution.</p>
</code> in their SKILL.md unlock those tools during execution, but filesystem scopes and shell blocklists still apply.</p>

<h3>Permission Modes</h3>
<p>At session start, Mercury asks you to choose a permission mode:</p>
<ul>
<li><strong>🔒 Ask Me</strong> — Mercury asks for confirmation before file writes, shell commands that need approval, and scope changes. Default on both CLI and Telegram.</li>
<li><strong>✅ Allow All</strong> — Mercury auto-approves all directory scopes, all shell commands (except blocked), and loop-continuation prompts. No interruptions during the session. Resets on restart.</li>
<li><strong>✅ Allow All</strong> — Mercury auto-approves shell commands that need confirmation (except blocked commands) and loop-continuation prompts. Filesystem scopes still apply and new paths can still require approval. Resets on restart.</li>
</ul>
<p>On <strong>CLI</strong>: an arrow-key menu appears at startup before the first prompt.</p>
<p>On <strong>Telegram</strong>: inline keyboard buttons appear with the first message. Use <code>/permissions</code> to change mode at any time.</p>
<p><strong>Scheduled tasks</strong> always run in Allow All mode — filesystem access and shell commands are auto-approved. You receive a notification before each task executes.</p>
<p><strong>Scheduled tasks</strong> always run with command and loop auto-approval, but shell blocklists and filesystem scoping still apply. You receive a notification before each task executes.</p>

<h2 id="second-brain">Second Brain</h2>
<p>Mercury has a persistent, structured memory that grows with every conversation. When enabled, it automatically extracts, stores, and retrieves facts about you — your preferences, goals, projects, habits, and more.</p>
Expand Down Expand Up @@ -1562,7 +1562,7 @@ <h3>Scheduling a skill</h3>
<li>"Remind me daily at 9am to run the daily-digest skill"</li>
</ul>
<p>Skills follow the <a href="https://agentskills.io" target="_blank" rel="noopener">Agent Skills specification</a>.
They get elevated permissions via <code>allowed-tools</code> and are loaded with progressive disclosure to save
They declare tool access via <code>allowed-tools</code>, while Mercury still enforces filesystem scopes and shell safety boundaries, and loads them with progressive disclosure to save
tokens.</p>

</main>
Expand Down
61 changes: 61 additions & 0 deletions docs/mercury-sandbox-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Mercury sandbox smoke test

This reproducible smoke test validates Mercury inside the sandbox with `glm-5.1` without changing agent logic.

Comment on lines +1 to +4
## What it automates

- manually loads `mercury-home/.env`
- exports `MERCURY_HOME`
- sets the working directory to the sandbox workspace
- starts Mercury in foreground mode with a PTY via `node dist/index.js start --foreground`
- selects `Ask Me` by sending `\r`
- sends `Reply with OK only.`
- verifies that the useful assistant response is exactly one line: `OK` (duplicate streamed output fails)
- stores both a raw transcript and an ANSI-stripped transcript

## Requirements

- `dist/index.js` must exist in the repo (`npm run build` if needed)
- `python3` with `pexpect` available
- a sandbox with:
- `MERCURY_SANDBOX_HOME` pointing to your `mercury-home` directory
- `MERCURY_SANDBOX_WORKSPACE` pointing to your sandbox workspace

By default, `scripts/run_mercury_sandbox_smoke.sh` derives sandbox paths from portable repo-relative locations:

- first choice: `$REPO_ROOT/sandbox/mercury-home` and `$REPO_ROOT/sandbox/workspace`
- fallback convenience: `$REPO_ROOT/../sandbox/mercury-home` and `$REPO_ROOT/../sandbox/workspace`

If your setup lives somewhere else, override the environment variables explicitly.

## Usage

From the repo root:

```bash
./scripts/run_mercury_sandbox_smoke.sh
```

You can optionally override paths or the prompt:

```bash
MERCURY_SANDBOX_HOME=/path/to/mercury-home \
MERCURY_SANDBOX_WORKSPACE=/path/to/workspace \
MERCURY_SMOKE_PROMPT='Reply with OK only.' \
./scripts/run_mercury_sandbox_smoke.sh
```

## Output

Transcripts are written to `tmp/mercury-smoke/`:

- `*.log`: raw terminal output
- `*.clean.txt`: cleaned output without ANSI sequences

The script fails if:

- `.env` is missing
- `dist/index.js` is missing
- startup does not show `glm-5.1`
- it cannot get past the permissions menu
- the assistant response is not exactly one line `OK` (including duplicated `OK` output)
137 changes: 137 additions & 0 deletions docs/permissions-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Mercury permission model

This document reflects the live permission wiring in the local repo, not the older scheduler/security notes.

## Summary

- Mercury keeps permission state per session/channel.
- `Ask Me` and `Allow All` are session modes, not global switches.
- `Allow All` enables auto-approval for the current session/channel only.
- `Allow All` does **not** add root filesystem scope.
- Internal flows and scheduled tasks also use auto-approval, but under that same restricted model.

## Session and channel isolation

Permission state lives inside `PermissionManager` session state keyed by channel. That state includes:

- `autoApproveAll`
- pending approvals
- temporary scopes
- channel type metadata

Practical consequence: enabling `Allow All` in one Telegram chat or one CLI session does not silently affect another channel.

Relevant files:

- `src/capabilities/permissions.ts`
- `src/capabilities/permissions.test.ts`

## Interactive modes

Interactive channels expose two modes:

- **Ask Me** — Mercury asks before risky shell commands, file writes, and permission escalations.
- **Allow All** — Mercury auto-approves those interactive prompts inside the current session/channel.

The current wiring is in `src/core/permission-mode.ts`:

```ts
permissions.setCurrentChannel(channelId, channelType);
permissions.setAutoApproveAll(mode === 'allow-all');
```

That function does **not** add `addTempScope('/')` or any equivalent root filesystem grant.

Relevant files:

- `src/core/permission-mode.ts`
- `src/core/permission-mode.test.ts`
- `src/index.ts`

## System messages: internal and scheduled

Mercury uses one permission model for user sessions and system-triggered runs.

`getMessagePermissionPolicy(...)` currently returns only:

- `autoApproveAll: true`

for:

- internal messages (`channelType === 'internal'`)
- scheduled/system messages (`senderId === 'system'` outside the internal channel)

This means scheduled runs are auto-approved, but they do not get a different unrestricted filesystem mode.

Relevant files:

- `src/core/agent.ts`
- `src/core/agent-permissions.test.ts`

## Scheduled task context

Scheduled tasks are not strictly “non-channel” anymore. When a task is created from a live channel, Mercury persists the origin context and replays the job as a system message tied to that source channel.

That preserves:

- correct delivery context
- session/channel isolation
- the same restricted auto-approval model

Relevant files:

- `src/capabilities/scheduler/schedule-task.ts`
- `src/capabilities/registry.ts`
- `src/core/agent.ts`

## What skill allowed-tools do

Skills can declare `allowed-tools` in `SKILL.md` to unlock specific tools while the skill is active.

Important boundary:

- `allowed-tools` do **not** bypass filesystem scopes
- `allowed-tools` do **not** bypass blocked shell commands
- filesystem access still requires an approved permanent or temporary scope

So the correct model is:

> Skills can unlock tool usage, but Mercury still enforces path boundaries and shell safety rules.

## What Allow All does not do

`Allow All` should not be documented as unrestricted filesystem access.

What still applies:

- shell blocklist
- filesystem scoping
- per-session/channel isolation

So the correct mental model is:

> `Allow All` removes confirmation prompts inside the current session. It does not remove Mercury's filesystem boundaries.

## Local patch consolidation status

The local repo now reflects a fully consolidated permission hardening pass with three practical outcomes:

1. `Allow All` is wired through `applySessionPermissionMode(...)` without root temp scope.
2. Internal and scheduled runs stay auto-approved without `addTempScope('/')`.
3. The message permission contract now models only the active behavior (`autoApproveAll`), with session state isolation covered by dedicated tests.

## Evidence and reproducibility

Useful verification points in this repo:

- `src/core/permission-mode.test.ts`
- `src/core/agent-permissions.test.ts`
- `src/capabilities/permissions.test.ts`
- `src/channels/telegram.test.ts`

For sandbox validation, use:

- `docs/mercury-sandbox-smoke.md`
- `scripts/run_mercury_sandbox_smoke.sh`

The smoke test validates the sandbox startup path with `glm-5.1`, manual `.env` loading, workspace `cwd`, interactive permission selection, and a minimal `OK` roundtrip.
Loading