mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-01 23:21:42 +00:00
* feat(codex): continue paired-node Codex catalog sessions from the Control UI Codex catalog rows on paired nodes were view-only. Node rows are now continuable when the node advertises and permits the codex threads.list, thread.turns.list, and cli.session.resume commands. Continue adopts the session into a locked codex OpenClaw session (deterministic harness:codex:node-session key, bounded native-history import, two-phase initializing marker) and installs a webchat conversation binding (codex-cli-node-session) through the new generic bindPluginSessionConversation seam with attempt-ID guarded rollback, so each web turn runs codex exec resume on the owning node via the existing inbound_claim path. Local continue/archive semantics are unchanged; archived rows are no longer offered as continuable. * fix(plugins): serialize plugin session binds per session Review P2: the bind rollback checked the attempt id and then awaited the unbind, so a concurrent Continue could install a newer binding between the two and lose it to the older attempt's rollback. All session binds for one session key now queue through an in-process tail, so a failing attempt can only ever roll back its own binding; the attempt-id guard stays as defense against out-of-band rebinds. * refactor(codex): extract node-continue and session-bind modules for the LOC ratchet Pure movement to satisfy the changed-file TypeScript size ratchet: the paired-node continue flow moves to session-catalog-node-continue.ts and session-catalog-node-adoption.ts, catalog paging/parsing helpers to session-catalog-parsing.ts, the Control UI session-bind seam to session-conversation-binding.ts (+ session-key helpers), and the internal conversation-id/channel-prefix helpers to utils. transcript-mirror.ts is restored to main byte-for-byte; the node history builder constructs a plain CodexThread (only id is required) instead of widening signatures. * fix(plugins): satisfy preserve-caught-error in the session-bind rollback * fix(codex): gate node continue on operator.admin and resume by CLI session id Second review round: (1) bound node turns pass canMutateCodexHost only for owners/admins, while sessions.catalog.continue is operator.write — a write-scoped Control UI client could adopt a node session whose every turn would then be rejected. The gateway now forwards the caller's scopes to the provider (SessionCatalogContinueProviderParams.clientScopes) and codex fails node continues closed without operator.admin, before the dedupe join so a non-admin cannot ride an admin's in-flight operation. (2) The node binding stored the app-server threadId, but codex exec resume takes the CLI session id and forked threads share a session tree where the two differ; the binding now prefers record.sessionId. * fix(codex): defer node-session restore until the binding is durable Third review round: continuing an archived adopted node session unarchived it before the gateway installed the conversation binding, so a failed bind left a visible session with no node routing. The unarchive now rides afterConversationBound; finalizeNodeAdoptedSession already covers both the initializing and finalized marker states, so the eager restore helper is deleted.
30 lines
847 B
TypeScript
30 lines
847 B
TypeScript
import crypto from "node:crypto";
|
|
import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce";
|
|
|
|
export const PLUGIN_BINDING_SESSION_PREFIX = "plugin-binding";
|
|
|
|
export function normalizeChannel(value: string): string {
|
|
return normalizeOptionalLowercaseString(value) ?? "";
|
|
}
|
|
|
|
export function buildPluginBindingSessionKey(params: {
|
|
pluginId: string;
|
|
channel: string;
|
|
accountId: string;
|
|
conversationId: string;
|
|
}): string {
|
|
const hash = crypto
|
|
.createHash("sha256")
|
|
.update(
|
|
JSON.stringify({
|
|
pluginId: params.pluginId,
|
|
channel: normalizeChannel(params.channel),
|
|
accountId: params.accountId,
|
|
conversationId: params.conversationId,
|
|
}),
|
|
)
|
|
.digest("hex")
|
|
.slice(0, 24);
|
|
return `${PLUGIN_BINDING_SESSION_PREFIX}:${params.pluginId}:${hash}`;
|
|
}
|