feat(cli): 1.29.0 — per-session IPC tokens + auto-scoping
every claudemesh launch-spawned session now mints a 32-byte random token, writes it under tmpdir (mode 0600), and registers it with the daemon. cli invocations from inside that session inherit CLAUDEMESH_IPC_TOKEN_FILE in env, attach the token via Authorization: ClaudeMesh-Session <hex>, and the daemon resolves it to a SessionInfo. server-side: every read route that filters by mesh now uses meshFromCtx — explicit query/body wins, session default fills in when missing. write routes follow the same pattern. cli-side: peers.ts (and other multi-mesh-iterating verbs in future) prefers session-token mesh over all joined meshes when the user didn't pass --mesh explicitly. backward-compatible in both directions — tokenless callers behave exactly as before. registry is in-memory; daemon restart loses it but the 30s reaper handles dead pids and most callers re-register on next launch. verified end-to-end: peer list with token returns 4 prueba1 peers, without token returns 3 meshes' peers (aggregate). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -653,6 +653,49 @@ export async function runLaunch(flags: LaunchFlags, rawArgs: string[]): Promise<
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
// 4b. Mint a per-session IPC token, persist it under tmpDir, and
|
||||
// register it with the daemon. The token's path is exposed to
|
||||
// the spawned claude (and all its descendants) via env so
|
||||
// CLI invocations from inside the session auto-attribute to it.
|
||||
let sessionTokenFilePath: string | null = null;
|
||||
let sessionTokenForCleanup: string | null = null;
|
||||
try {
|
||||
const { mintSessionToken, TOKEN_FILE_ENV } = await import("~/services/session/token.js");
|
||||
const minted = mintSessionToken(tmpDir);
|
||||
sessionTokenFilePath = minted.filePath;
|
||||
sessionTokenForCleanup = minted.token;
|
||||
|
||||
// Register with the daemon. Best-effort: a daemon failure here
|
||||
// means the session falls back to user-level scope, which is fine.
|
||||
const { ipc } = await import("~/daemon/ipc/client.js");
|
||||
const sessionIdForRegister = claudeSessionId ?? randomUUID();
|
||||
await ipc({
|
||||
method: "POST",
|
||||
path: "/v1/sessions/register",
|
||||
timeoutMs: 3_000,
|
||||
body: {
|
||||
token: minted.token,
|
||||
session_id: sessionIdForRegister,
|
||||
mesh: mesh.slug,
|
||||
display_name: displayName,
|
||||
pid: process.pid,
|
||||
cwd: process.cwd(),
|
||||
...(role ? { role } : {}),
|
||||
...(parsedGroups.length > 0 ? { groups: parsedGroups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`) } : {}),
|
||||
},
|
||||
}).catch(() => null);
|
||||
|
||||
// Pin the env name on a global so the spawn block below can pick it up.
|
||||
(process as unknown as { _claudemeshTokenEnv?: { name: string; value: string } })._claudemeshTokenEnv = {
|
||||
name: TOKEN_FILE_ENV,
|
||||
value: minted.filePath,
|
||||
};
|
||||
} catch {
|
||||
// Token mint or registration failed — proceed without per-session
|
||||
// attribution. CLI invocations from the session will still work,
|
||||
// they'll just default to user-level scope.
|
||||
}
|
||||
|
||||
// 5. Print summary banner (wizard already handled all interactive config).
|
||||
if (!args.quiet) {
|
||||
printBanner(displayName, mesh.slug, role, parsedGroups, messageMode);
|
||||
@@ -774,7 +817,14 @@ export async function runLaunch(flags: LaunchFlags, rawArgs: string[]): Promise<
|
||||
writeFileSync(claudeConfigPath, JSON.stringify(claudeConfig, null, 2) + "\n", "utf-8");
|
||||
} catch { /* best effort */ }
|
||||
}
|
||||
// Ephemeral config dir
|
||||
// The token's session-token file lives inside tmpDir; rmSync below
|
||||
// shreds the secret. The daemon's session reaper notices the
|
||||
// launched session's pid is gone within 30s and drops the registry
|
||||
// entry. Explicit DELETE on /v1/sessions is feasible only from an
|
||||
// async exit hook, which adds complexity for ~30s of memory the
|
||||
// reaper will reclaim anyway. Leaving as-is; revisit if the
|
||||
// registry ever grows persistence.
|
||||
// Ephemeral config dir (also drops the session-token file)
|
||||
try {
|
||||
rmSync(tmpDir, { recursive: true, force: true });
|
||||
} catch { /* best effort */ }
|
||||
@@ -836,6 +886,7 @@ export async function runLaunch(flags: LaunchFlags, rawArgs: string[]): Promise<
|
||||
CLAUDEMESH_CONFIG_DIR: tmpDir,
|
||||
CLAUDEMESH_DISPLAY_NAME: displayName,
|
||||
...(claudeSessionId ? { CLAUDEMESH_SESSION_ID: claudeSessionId } : {}),
|
||||
...(sessionTokenFilePath ? { CLAUDEMESH_IPC_TOKEN_FILE: sessionTokenFilePath } : {}),
|
||||
MCP_TIMEOUT: process.env.MCP_TIMEOUT ?? "30000",
|
||||
MAX_MCP_OUTPUT_TOKENS: process.env.MAX_MCP_OUTPUT_TOKENS ?? "50000",
|
||||
...(role ? { CLAUDEMESH_ROLE: role } : {}),
|
||||
|
||||
Reference in New Issue
Block a user