refactor(gateway): unify terminal restriction tracking (#122228)

This commit is contained in:
Vincent Koc
2026-08-12 05:54:15 +08:00
committed by GitHub
parent 8d034a7b61
commit 642fe77624
2 changed files with 63 additions and 40 deletions
+34
View File
@@ -81,6 +81,40 @@ describe("createTerminalLaunchPolicy", () => {
}
});
it("keeps restart and commit restrictions isolated across agents", () => {
const baseConfig: OpenClawConfig = {
agents: { list: [{ id: "alpha" }, { id: "beta" }] },
};
const policy = createTerminalLaunchPolicy(baseConfig);
policy.prepareConfig(
{
agents: {
list: [{ id: "alpha", sandbox: { mode: "all" } }, { id: "beta" }],
},
},
{ restartPending: true },
);
policy.prepareConfig(
{
agents: {
list: [{ id: "alpha" }, { id: "beta", sandbox: { mode: "all" } }],
},
},
{ restartPending: false },
);
expect(policy.resolve("alpha").ok).toBe(false);
expect(policy.resolve("beta").ok).toBe(false);
policy.acceptConfig({ retireRejectedRestart: false });
expect(policy.resolve("alpha").ok).toBe(false);
expect(policy.resolve("beta").ok).toBe(true);
policy.acceptConfig({ retireRejectedRestart: true });
expect(policy.resolve("alpha").ok).toBe(true);
});
it("keeps current launch details until a restart-bound change takes effect", () => {
const workspace = tempDirs.make("term-policy-");
const policy = createTerminalLaunchPolicy({
+29 -40
View File
@@ -120,12 +120,14 @@ function resolveTerminalLaunch(params: {
export function createTerminalLaunchPolicy(initialConfig: OpenClawConfig): TerminalLaunchPolicy {
let activeConfig = initialConfig;
let hasPendingRestart = false;
let terminalDisabledUntilRestart = false;
let preparedConfig: OpenClawConfig | null = null;
let appliedConfigWhileRestartPending: OpenClawConfig | null = null;
let terminalDisabledUntilCommit = false;
const blockedAgentsUntilRestart = new Map<string, TerminalLaunchBlock>();
const blockedAgentsUntilCommit = new Map<string, TerminalLaunchBlock>();
const createRestrictions = () => ({
disabled: false,
blockedAgents: new Map<string, TerminalLaunchBlock>(),
});
const restartRestrictions = createRestrictions();
const commitRestrictions = createRestrictions();
const preserveTerminalConfig = (config: OpenClawConfig, owner: OpenClawConfig) => {
const { terminal: _ignored, ...gateway } = config.gateway ?? {};
const terminal = owner.gateway?.terminal;
@@ -146,9 +148,12 @@ export function createTerminalLaunchPolicy(initialConfig: OpenClawConfig): Termi
configuredShell: terminalConfig?.shell,
});
};
const accumulateRestartRestrictions = (config: OpenClawConfig) => {
const accumulateRestrictions = (
config: OpenClawConfig,
restrictions: ReturnType<typeof createRestrictions>,
) => {
if (!isTerminalConfigEnabled(config)) {
terminalDisabledUntilRestart = true;
restrictions.disabled = true;
return;
}
const activeAgentIds = new Set([
@@ -158,25 +163,13 @@ export function createTerminalLaunchPolicy(initialConfig: OpenClawConfig): Termi
for (const agentId of activeAgentIds) {
const candidate = resolveForConfig(config, agentId);
if (!candidate.ok) {
blockedAgentsUntilRestart.set(agentId, candidate.block);
restrictions.blockedAgents.set(agentId, candidate.block);
}
}
};
const accumulateCommitRestrictions = (config: OpenClawConfig) => {
if (!isTerminalConfigEnabled(config)) {
terminalDisabledUntilCommit = true;
return;
}
const activeAgentIds = new Set([
...listAgentIds(activeConfig),
resolveDefaultAgentId(activeConfig),
]);
for (const agentId of activeAgentIds) {
const candidate = resolveForConfig(config, agentId);
if (!candidate.ok) {
blockedAgentsUntilCommit.set(agentId, candidate.block);
}
}
const clearRestrictions = (restrictions: ReturnType<typeof createRestrictions>) => {
restrictions.disabled = false;
restrictions.blockedAgents.clear();
};
return {
@@ -185,14 +178,14 @@ export function createTerminalLaunchPolicy(initialConfig: OpenClawConfig): Termi
if (!active.ok) {
return active;
}
if (terminalDisabledUntilRestart) {
if (restartRestrictions.disabled) {
return { ok: false, block: { kind: "disabled" } };
}
const pendingBlock = blockedAgentsUntilRestart.get(active.plan.agentId);
const pendingBlock = restartRestrictions.blockedAgents.get(active.plan.agentId);
if (pendingBlock) {
return { ok: false, block: pendingBlock };
}
const preparedBlock = blockedAgentsUntilCommit.get(active.plan.agentId);
const preparedBlock = commitRestrictions.blockedAgents.get(active.plan.agentId);
if (preparedBlock) {
return { ok: false, block: preparedBlock };
}
@@ -207,8 +200,8 @@ export function createTerminalLaunchPolicy(initialConfig: OpenClawConfig): Termi
},
isEnabled: () =>
isTerminalConfigEnabled(activeConfig) &&
!terminalDisabledUntilRestart &&
!terminalDisabledUntilCommit &&
!restartRestrictions.disabled &&
!commitRestrictions.disabled &&
(preparedConfig === null || isTerminalConfigEnabled(preparedConfig)),
prepareConfig: (config, options) => {
if (options.restartPending) {
@@ -216,7 +209,7 @@ export function createTerminalLaunchPolicy(initialConfig: OpenClawConfig): Termi
// Keep an older candidate fail-closed only until this transaction is
// accepted; do not mix its restrictions into the restart-owned bucket.
preparedConfig = null;
accumulateRestartRestrictions(config);
accumulateRestrictions(config, restartRestrictions);
return;
}
// No-op/hot plans may arrive with restart-only terminal fields that an
@@ -224,11 +217,11 @@ export function createTerminalLaunchPolicy(initialConfig: OpenClawConfig): Termi
// terminal subtree already owned by the active or pending process.
if (hasPendingRestart) {
preparedConfig = preserveTerminalConfig(config, activeConfig);
accumulateCommitRestrictions(preparedConfig);
accumulateRestrictions(preparedConfig, commitRestrictions);
return;
}
preparedConfig = preserveTerminalConfig(config, activeConfig);
accumulateCommitRestrictions(preparedConfig);
accumulateRestrictions(preparedConfig, commitRestrictions);
},
commitConfig: () => {
if (hasPendingRestart) {
@@ -238,10 +231,9 @@ export function createTerminalLaunchPolicy(initialConfig: OpenClawConfig): Termi
appliedConfigWhileRestartPending = preparedConfig;
}
preparedConfig = null;
terminalDisabledUntilCommit = false;
blockedAgentsUntilCommit.clear();
clearRestrictions(commitRestrictions);
if (appliedConfigWhileRestartPending) {
accumulateCommitRestrictions(appliedConfigWhileRestartPending);
accumulateRestrictions(appliedConfigWhileRestartPending, commitRestrictions);
}
return;
}
@@ -249,20 +241,17 @@ export function createTerminalLaunchPolicy(initialConfig: OpenClawConfig): Termi
activeConfig = preparedConfig;
}
preparedConfig = null;
terminalDisabledUntilCommit = false;
blockedAgentsUntilCommit.clear();
clearRestrictions(commitRestrictions);
},
acceptConfig: (options) => {
// Baseline acceptance retires an un-published candidate, including config
// intentionally skipped by reload policy. Only onConfigApplied may stage
// runtime truth for promotion after a rejected restart.
preparedConfig = null;
terminalDisabledUntilCommit = false;
blockedAgentsUntilCommit.clear();
clearRestrictions(commitRestrictions);
if (options.retireRejectedRestart) {
hasPendingRestart = false;
terminalDisabledUntilRestart = false;
blockedAgentsUntilRestart.clear();
clearRestrictions(restartRestrictions);
if (appliedConfigWhileRestartPending) {
activeConfig = appliedConfigWhileRestartPending;
}
@@ -270,7 +259,7 @@ export function createTerminalLaunchPolicy(initialConfig: OpenClawConfig): Termi
return;
}
if (appliedConfigWhileRestartPending) {
accumulateCommitRestrictions(appliedConfigWhileRestartPending);
accumulateRestrictions(appliedConfigWhileRestartPending, commitRestrictions);
}
},
};