Layer 2 MCP Watcher v0 scaffold
Per spec/agent-watcher.md §4. TypeScript/Node implementation living in
mcp-watcher/ subdirectory, parallel to Layer 1 Collector at repo root.
What lands:
- Core MCP server (src/server.ts) with experimental['claude/channel']:{}
+ tools:{} capability declarations, stdio transport, channel-event
notifier wired through the inbox watcher.
- Identity resolution mirroring agent-ping's layered model
(PING_AGENT_IDENTITY env, $CLAUDE_HOME/ping-agent, ~/.ping-agent).
- Inbox reader with HWM tracking, sentinel deferral (warn-after-3),
atomic HWM writes via tmp+rename.
- chokidar-backed file watcher with coalesced drain, urgent-first
ordering, recentEvents map for tool sender lookup.
- Three reply tools (ack / respond / mark_handled) with cross-host
write discipline (writes to local inbox files; Syncthing replicates).
- Sentinel file (.<agent>.watcher-active) for hook coexistence per
spec §4.3 — agent-ping hook stands down when the watcher is in
charge of delivery on this host. Sentinel + hwm in .stignore.
- 35 unit tests passing (vitest): inbox parsing, HWM round-trip,
sentinel deferral semantics, identity layers, tool I/O, watcher
drain + ordering + restart-from-hwm.
- install.sh (Angus-executed, rule-2 compliant) installs deps,
builds, symlinks ~/.local/bin/agent-watcher-mcp, prints mcp.json
registration snippet for paste.
- README documents launch flag, sandbox CLAUDE_HOME pattern,
hook coexistence, observability, v2 limitations.
Not yet:
- Integration test against a real Claude Code session — gated on
Angus spinning up a sandbox CC session on the VPS with
CLAUDE_HOME=~/.claude-sandbox.
- agent-ping hook update to read the sentinel and stand down.
Separate small PR against agent-ping.
Interface contract with Layer 1: the inbox JSONL line shape from
inbox.ts::PingEvent matches inbox.Event in the Collector — bit-
identical reads regardless of source.
This commit is contained in:
parent
3eda72df28
commit
c22558c67a
18 changed files with 4786 additions and 0 deletions
33
mcp-watcher/src/paths.ts
Normal file
33
mcp-watcher/src/paths.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Path helpers — single source of truth for where files live.
|
||||
//
|
||||
// All paths derive from $HOME (or os.homedir() fallback) and the agent's
|
||||
// resolved identity. Centralizing them here keeps inbox, hwm, sentinel,
|
||||
// and ack-log paths consistent across modules and trivially mockable in
|
||||
// tests by overriding `home`.
|
||||
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
export interface Paths {
|
||||
workspace: string;
|
||||
pingsDir: string;
|
||||
acksLog: string;
|
||||
agentsFile: string;
|
||||
inbox(agent: string): string;
|
||||
hwm(agent: string): string;
|
||||
watcherActive(agent: string): string;
|
||||
}
|
||||
|
||||
export function makePaths(home: string = process.env.HOME ?? homedir()): Paths {
|
||||
const workspace = join(home, "Nyx", "workspace");
|
||||
const pingsDir = join(workspace, "pings");
|
||||
return {
|
||||
workspace,
|
||||
pingsDir,
|
||||
acksLog: join(workspace, "acks", "log"),
|
||||
agentsFile: join(pingsDir, ".agents"),
|
||||
inbox: (agent: string) => join(pingsDir, `${agent}.inbox`),
|
||||
hwm: (agent: string) => join(pingsDir, `.${agent}.hwm`),
|
||||
watcherActive: (agent: string) => join(pingsDir, `.${agent}.watcher-active`),
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue