Layer 2 MCP Watcher v0 scaffold #2
4 changed files with 50 additions and 17 deletions
|
|
@ -49,7 +49,7 @@ won't compete with the default `~/.ping-agent` for inbox reads):
|
||||||
```bash
|
```bash
|
||||||
mkdir -p ~/.claude-sandbox
|
mkdir -p ~/.claude-sandbox
|
||||||
echo sandbox > ~/.claude-sandbox/ping-agent
|
echo sandbox > ~/.claude-sandbox/ping-agent
|
||||||
CLAUDE_HOME=~/.claude-sandbox \
|
CLAUDE_CONFIG_DIR=~/.claude-sandbox \
|
||||||
claude --dangerously-load-development-channels server:agent-watcher
|
claude --dangerously-load-development-channels server:agent-watcher
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ Then start Claude Code with the development flag (research preview):
|
||||||
|
|
||||||
To use a sandbox identity instead of the default ~/.ping-agent:
|
To use a sandbox identity instead of the default ~/.ping-agent:
|
||||||
|
|
||||||
CLAUDE_HOME=~/.claude-sandbox claude --dangerously-load-development-channels server:agent-watcher
|
CLAUDE_CONFIG_DIR=~/.claude-sandbox claude --dangerously-load-development-channels server:agent-watcher
|
||||||
|
|
||||||
(Drop a 'ping-agent' file in ~/.claude-sandbox containing the sandbox name first.)
|
(Drop a 'ping-agent' file in ~/.claude-sandbox containing the sandbox name first.)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,11 @@ export interface ResolvedIdentity {
|
||||||
* Resolve this process's agent identity.
|
* Resolve this process's agent identity.
|
||||||
* Layers, first match wins:
|
* Layers, first match wins:
|
||||||
* 1. PING_AGENT_IDENTITY env var
|
* 1. PING_AGENT_IDENTITY env var
|
||||||
* 2. $CLAUDE_HOME/ping-agent (if CLAUDE_HOME is set and file exists)
|
* 2. $CLAUDE_CONFIG_DIR/ping-agent (the env var Claude Code actually reads;
|
||||||
* 3. ~/.ping-agent
|
* verified by `strings` on the binary, 2026-05-06 sandbox spike).
|
||||||
|
* 3. $CLAUDE_HOME/ping-agent (kept for compat with anyone setting the
|
||||||
|
* previously-documented variable).
|
||||||
|
* 4. ~/.ping-agent
|
||||||
*
|
*
|
||||||
* Throws if no layer resolves.
|
* Throws if no layer resolves.
|
||||||
*/
|
*/
|
||||||
|
|
@ -30,9 +33,10 @@ export function resolveIdentity(env: NodeJS.ProcessEnv = process.env): ResolvedI
|
||||||
return { agent: envId, source: "PING_AGENT_IDENTITY env var" };
|
return { agent: envId, source: "PING_AGENT_IDENTITY env var" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const claudeHome = (env.CLAUDE_HOME ?? "").trim();
|
for (const varName of ["CLAUDE_CONFIG_DIR", "CLAUDE_HOME"]) {
|
||||||
if (claudeHome) {
|
const dir = (env[varName] ?? "").trim();
|
||||||
const f = join(claudeHome, "ping-agent");
|
if (!dir) continue;
|
||||||
|
const f = join(dir, "ping-agent");
|
||||||
if (existsSync(f)) {
|
if (existsSync(f)) {
|
||||||
const name = readFileSync(f, "utf8").trim();
|
const name = readFileSync(f, "utf8").trim();
|
||||||
if (name) {
|
if (name) {
|
||||||
|
|
@ -52,6 +56,6 @@ export function resolveIdentity(env: NodeJS.ProcessEnv = process.env): ResolvedI
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`agent-watcher-mcp: no identity resolved. Set PING_AGENT_IDENTITY, ` +
|
`agent-watcher-mcp: no identity resolved. Set PING_AGENT_IDENTITY, ` +
|
||||||
`$CLAUDE_HOME/ping-agent, or ${defaultFile}.`,
|
`$CLAUDE_CONFIG_DIR/ping-agent (or $CLAUDE_HOME/ping-agent), or ${defaultFile}.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,20 +23,49 @@ describe("resolveIdentity", () => {
|
||||||
expect(r.source).toMatch(/env/i);
|
expect(r.source).toMatch(/env/i);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses $CLAUDE_HOME/ping-agent next", () => {
|
it("uses $CLAUDE_CONFIG_DIR/ping-agent next", () => {
|
||||||
writeFileSync(join(dir, ".ping-agent"), "default-agent\n");
|
writeFileSync(join(dir, ".ping-agent"), "default-agent\n");
|
||||||
const claudeHome = join(dir, "claude-sandbox");
|
const sandbox = join(dir, "claude-sandbox");
|
||||||
|
mkdirSync(sandbox);
|
||||||
|
writeFileSync(join(sandbox, "ping-agent"), "sandbox\n");
|
||||||
|
const r = resolveIdentity({
|
||||||
|
HOME: dir,
|
||||||
|
CLAUDE_CONFIG_DIR: sandbox,
|
||||||
|
PING_AGENT_IDENTITY: "",
|
||||||
|
});
|
||||||
|
expect(r.agent).toBe("sandbox");
|
||||||
|
expect(r.source).toContain(sandbox);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to $CLAUDE_HOME/ping-agent when CLAUDE_CONFIG_DIR is unset", () => {
|
||||||
|
writeFileSync(join(dir, ".ping-agent"), "default-agent\n");
|
||||||
|
const claudeHome = join(dir, "claude-old");
|
||||||
mkdirSync(claudeHome);
|
mkdirSync(claudeHome);
|
||||||
writeFileSync(join(claudeHome, "ping-agent"), "sandbox\n");
|
writeFileSync(join(claudeHome, "ping-agent"), "compat\n");
|
||||||
const r = resolveIdentity({
|
const r = resolveIdentity({
|
||||||
HOME: dir,
|
HOME: dir,
|
||||||
CLAUDE_HOME: claudeHome,
|
CLAUDE_HOME: claudeHome,
|
||||||
PING_AGENT_IDENTITY: "",
|
PING_AGENT_IDENTITY: "",
|
||||||
});
|
});
|
||||||
expect(r.agent).toBe("sandbox");
|
expect(r.agent).toBe("compat");
|
||||||
expect(r.source).toContain(claudeHome);
|
expect(r.source).toContain(claudeHome);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("CLAUDE_CONFIG_DIR takes precedence over CLAUDE_HOME", () => {
|
||||||
|
const sandbox = join(dir, "claude-sandbox");
|
||||||
|
const old = join(dir, "claude-old");
|
||||||
|
mkdirSync(sandbox);
|
||||||
|
mkdirSync(old);
|
||||||
|
writeFileSync(join(sandbox, "ping-agent"), "new\n");
|
||||||
|
writeFileSync(join(old, "ping-agent"), "old\n");
|
||||||
|
const r = resolveIdentity({
|
||||||
|
HOME: dir,
|
||||||
|
CLAUDE_CONFIG_DIR: sandbox,
|
||||||
|
CLAUDE_HOME: old,
|
||||||
|
});
|
||||||
|
expect(r.agent).toBe("new");
|
||||||
|
});
|
||||||
|
|
||||||
it("falls through to ~/.ping-agent", () => {
|
it("falls through to ~/.ping-agent", () => {
|
||||||
writeFileSync(join(dir, ".ping-agent"), "bob\n");
|
writeFileSync(join(dir, ".ping-agent"), "bob\n");
|
||||||
const r = resolveIdentity({ HOME: dir });
|
const r = resolveIdentity({ HOME: dir });
|
||||||
|
|
@ -48,12 +77,12 @@ describe("resolveIdentity", () => {
|
||||||
expect(() => resolveIdentity({ HOME: dir })).toThrow(/no identity/);
|
expect(() => resolveIdentity({ HOME: dir })).toThrow(/no identity/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ignores empty CLAUDE_HOME/ping-agent and falls through", () => {
|
it("ignores empty CLAUDE_CONFIG_DIR/ping-agent and falls through", () => {
|
||||||
writeFileSync(join(dir, ".ping-agent"), "bob\n");
|
writeFileSync(join(dir, ".ping-agent"), "bob\n");
|
||||||
const claudeHome = join(dir, "claude-sandbox");
|
const sandbox = join(dir, "claude-sandbox");
|
||||||
mkdirSync(claudeHome);
|
mkdirSync(sandbox);
|
||||||
writeFileSync(join(claudeHome, "ping-agent"), "");
|
writeFileSync(join(sandbox, "ping-agent"), "");
|
||||||
const r = resolveIdentity({ HOME: dir, CLAUDE_HOME: claudeHome });
|
const r = resolveIdentity({ HOME: dir, CLAUDE_CONFIG_DIR: sandbox });
|
||||||
expect(r.agent).toBe("bob");
|
expect(r.agent).toBe("bob");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue