Skip to content
Merged
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
10 changes: 5 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ its agent-runtime patterns (the local memory daemon was deliberately ported from
`AGENTAGE_NO_DAEMON=1`, or fork blocked)
- `src/daemon/` + `src/daemon-entry.ts` - the local daemon (node:http, 127.0.0.1 only): one
in-process engine serialises vault mutations; `agentage daemon start|stop|status`
- `src/sync/` - channel-based sync; one subfolder per channel, no shared root logic:
- `src/sync/` - channel-based sync; one subfolder per channel, no shared root logic. **Git is the
only sync channel.** Account (`agentage`-origin) vaults have NO sync channel: they are registered,
provisioned in the account, and served locally over MCP, but nothing moves files between the two.
`status` renders them `account ✗ not synced` and `vault sync` says so instead of claiming success -
keep both honest if this ever changes:
- `src/sync/git/` - git channel (M4): `manager` (scheduler + status), `cycle` (one commit+push /
pull-rebase round), `planner` (targets + interval math), `git-exec` (spawn git, error classify),
`remote-url` (allowlist + redact), `conflict` (`<file>.conflict.md` naming). The daemon acts on
per-vault `origin[]` (external remotes only), debounced per `interval` (SECONDS; 0 = manual-only);
conflicts keep both sides (zero lost writes); `ignore` rides on `.git/info/exclude` (defaults
`.obsidian/` + `data.json`, a set value REPLACES them, `[]` = all). `spawn git` directly
(memory-core's createGit is private); no new deps
- `src/sync/couch/` - couch channel (account vaults): `manager` (thin composition), `manager.types`
(shared shapes), `cycle` / `wire` / `push-on-write` (the sync-on-save + scheduling seams),
`mutation-target` (verb -> vault+path), `local-commit`, `discovery`, `file-store`, `state-store`,
`targets`
- `src/sync/discover/` - `watcher` (auto-discovers new vaults, provisions account vaults)
- `vault sync [name]` forces a git cycle via the daemon (`/api/sync/run`) or in-process when down
- `src/package-guard.test.ts` - CI guard: no agent-runtime remnants (express/ws/sqlite/
Expand Down
38 changes: 31 additions & 7 deletions e2e/account-vault.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { expect, test } from '@playwright/test';
import { assertCliBuilt, createCliMachine } from './helpers.js';
import { assertCliBuilt, createCliMachine, plain, statusJson } from './helpers.js';

// Account vaults (agentage channel) are offline-first: a no-flag `vault add` writes the local
// entry and mirror dir with zero network, provisioning the cloud channel only when signed in.
// Account vaults are offline-first: a no-flag `vault add` writes the local entry and folder with
// zero network, creating the account-side memory only when signed in. They have no sync channel,
// which `status` and `vault sync` must state outright rather than imply health or stay silent.
// Egress is blackholed via unroutable proxies to prove the offline path never reaches out. @p0
const BLACKHOLE = 'http://127.0.0.1:1';
const OFFLINE = {
Expand Down Expand Up @@ -95,17 +96,40 @@ test.describe('account vault (offline) @p0 @offline', () => {
}
});

test('vault sync on an offline, signed-out account vault pauses instead of erroring', async () => {
test('vault sync names an account vault as unsynced instead of claiming success', async () => {
const m = createCliMachine(OFFLINE);
try {
const vaultDir = join(m.configDir, 'acct');
expect((await m.exec(['vault', 'add', 'acct', '--path', vaultDir])).code).toBe(0);

// Not signed in: the couch cycle pauses with zero network, never a crash (exit 0).
// An account vault has no sync channel: say so, never a crash (exit 0), never a success line.
const sync = await m.exec(['vault', 'sync', 'acct']);
expect(sync.code, sync.stderr).toBe(0);
expect(sync.stdout).toContain('acct (account)');
expect(sync.stdout).toContain('paused (signed out)');
const out = plain(sync.stdout);
expect(out).toContain('acct (account): not synced - account vaults have no sync channel');
expect(out).not.toContain('up to date');
expect(out).not.toContain('Syncing');
} finally {
m.cleanup();
}
});

test('status lists an account vault as not synced rather than healthy or hidden', async () => {
const m = createCliMachine(OFFLINE);
try {
const vaultDir = join(m.configDir, 'acct');
expect((await m.exec(['vault', 'add', 'acct', '--path', vaultDir])).code).toBe(0);

const report = await statusJson(m);
const acct = report.vaults.find((v) => v.name === 'acct');
expect(acct, 'account vault missing from status').toBeDefined();
expect(acct!.channel).toBe('account');
expect(acct!.status).toBe('unsynced');

const human = await m.exec(['status']);
expect(human.code, human.stderr).toBe(0);
expect(plain(human.stdout)).toContain('not synced - account vaults have no sync channel');
expect(plain(human.stdout)).not.toContain('connected');
} finally {
m.cleanup();
}
Expand Down
Loading