Files
openclaw/extensions/anthropic/session-catalog-registration.ts
Peter Steinberger b54034d5c2 fix: isolated gateways list and open the operator's HOME Claude/Codex/OpenCode/Pi sessions (#122983)
* fix(sessions): isolated gateways no longer inherit HOME external session catalogs

A gateway on isolated state (custom OPENCLAW_STATE_DIR/CONFIG_PATH/OPENCLAW_HOME,
relocated home, or any named profile) listed, read, continued, archived, and
reopened the operator's real Claude Code/Codex/OpenCode/Pi sessions from the
process HOME. External catalogs now require the default install identity for
process-HOME scans: every catalog verb receives the isolation policy and rejects
HOME-fallback local targets, unknown providers fail closed unless they declare
supportsProcessHomeIsolation, and one structured warning records the skip.
Paired-node hosts and explicitly rooted stores (CLAUDE_CONFIG_DIR, CODEX_HOME,
OPENCODE_DB, Pi session dirs) keep working; default-identity gateways are
unchanged.

* fix(sessions): inject catalog HOME-isolation fact at registry construction

* chore(sdk): regenerate plugin API baselines after rebase

* chore(sdk): regenerate plugin API baselines after rebase
2026-08-13 01:12:03 -07:00

145 lines
5.2 KiB
TypeScript

import { statSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import {
createLazyRuntimeModule,
createLazyRuntimeSurface,
} from "openclaw/plugin-sdk/lazy-runtime";
import type {
OpenClawPluginApi,
OpenClawPluginNodeHostCommand,
OpenClawPluginNodeInvokePolicy,
} from "openclaw/plugin-sdk/plugin-entry";
import type { SessionCatalogProvider } from "openclaw/plugin-sdk/session-catalog";
import { CLAUDE_CLI_BACKEND_ID, CLAUDE_CLI_ROUTE_PROBE_MODEL_IDS } from "./cli-constants.js";
import { resolveClaudeTerminalExecutable } from "./session-catalog-executable.js";
import {
CLAUDE_CLI_NODE_RUN_COMMAND,
CLAUDE_SESSION_READ_COMMAND,
CLAUDE_SESSIONS_LIST_COMMAND,
CLAUDE_TERMINAL_RESUME_COMMAND,
} from "./session-catalog-shared.js";
const CLAUDE_SESSIONS_CAPABILITY = "claude-sessions";
const loadClaudeSessionNodeCommands = createLazyRuntimeModule(
() => import("./session-catalog-node-commands.js"),
);
function isClaudeSessionCatalogEnabled(pluginConfig: unknown): boolean {
if (!pluginConfig || typeof pluginConfig !== "object") {
return true;
}
const sessionCatalog = (pluginConfig as { sessionCatalog?: unknown }).sessionCatalog;
return !(
sessionCatalog &&
typeof sessionCatalog === "object" &&
(sessionCatalog as { enabled?: unknown }).enabled === false
);
}
// Node declarations expose catalog commands only when this machine owns a
// Claude session store; otherwise the gateway must skip the node capability.
function claudeProjectsAvailable(env: NodeJS.ProcessEnv): boolean {
const homeDir = env.HOME?.trim() || env.USERPROFILE?.trim() || os.homedir();
const configDir = env.CLAUDE_CONFIG_DIR?.trim();
try {
return statSync(
path.join(configDir ? path.resolve(configDir) : path.join(homeDir, ".claude"), "projects"),
).isDirectory();
} catch {
return false;
}
}
function currentConfig(api: OpenClawPluginApi): OpenClawConfig {
return (api.runtime.config?.current?.() ?? api.config ?? {}) as OpenClawConfig;
}
function registerClaudeSessionCatalog(api: OpenClawPluginApi): void {
const loadCatalogRuntime = createLazyRuntimeSurface(
() => import("./session-catalog.js"),
(module) => module.createClaudeSessionCatalogRuntime(api),
);
const provider: SessionCatalogProvider = {
id: "claude",
label: "Claude Code",
supportsProcessHomeIsolation: true,
resolveCreateSession: ({ agentId }) =>
api.runtime.agent.resolveSessionCatalogCreateTarget({
config: currentConfig(api),
requestedAgentId: agentId,
provider: "anthropic",
modelIds: CLAUDE_CLI_ROUTE_PROBE_MODEL_IDS,
agentRuntime: CLAUDE_CLI_BACKEND_ID,
}),
list: async (query) => await (await loadCatalogRuntime()).list(query),
read: async (request) => await (await loadCatalogRuntime()).read(request),
continueSession: async (request) => await (await loadCatalogRuntime()).continueSession(request),
startTerminalSession: async (request) =>
await (await loadCatalogRuntime()).startTerminalSession(request),
openTerminal: async (request) => await (await loadCatalogRuntime()).openTerminal(request),
checkUpstreamActivity: async (probes, policy) =>
await (await loadCatalogRuntime()).checkUpstreamActivity(probes, policy),
};
api.registerSessionCatalog(provider);
}
function createClaudeSessionNodeHostCommands(): OpenClawPluginNodeHostCommand[] {
return [
{
command: CLAUDE_SESSIONS_LIST_COMMAND,
cap: CLAUDE_SESSIONS_CAPABILITY,
dangerous: false,
isAvailable: ({ env }) => claudeProjectsAvailable(env),
handle: async (paramsJSON) =>
await (await loadClaudeSessionNodeCommands()).listClaudeSessions(paramsJSON),
},
{
command: CLAUDE_SESSION_READ_COMMAND,
cap: CLAUDE_SESSIONS_CAPABILITY,
dangerous: false,
isAvailable: ({ env }) => claudeProjectsAvailable(env),
handle: async (paramsJSON) =>
await (await loadClaudeSessionNodeCommands()).readClaudeSession(paramsJSON),
},
{
command: CLAUDE_TERMINAL_RESUME_COMMAND,
cap: CLAUDE_SESSIONS_CAPABILITY,
dangerous: false,
duplex: true,
isAvailable: ({ env }) =>
claudeProjectsAvailable(env) && Boolean(resolveClaudeTerminalExecutable(env)),
handle: async (paramsJSON, io) =>
await (await loadClaudeSessionNodeCommands()).resumeClaudeSession(paramsJSON, io),
},
];
}
export function createClaudeSessionNodeInvokePolicies(): OpenClawPluginNodeInvokePolicy[] {
return [
{
commands: [
CLAUDE_SESSIONS_LIST_COMMAND,
CLAUDE_SESSION_READ_COMMAND,
CLAUDE_CLI_NODE_RUN_COMMAND,
CLAUDE_TERMINAL_RESUME_COMMAND,
],
defaultPlatforms: ["macos", "linux", "windows"],
handle: (context) =>
context.command === CLAUDE_TERMINAL_RESUME_COMMAND ? { ok: true } : context.invokeNode(),
},
];
}
export function registerClaudeSessionDiscovery(api: OpenClawPluginApi): void {
if (!isClaudeSessionCatalogEnabled(api.pluginConfig)) {
return;
}
registerClaudeSessionCatalog(api);
for (const command of createClaudeSessionNodeHostCommands()) {
api.registerNodeHostCommand(command);
}
}