mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-04 07:41:36 +00:00
* refactor(agents): require explicit roster defaults * feat(onboard): create named first roster agent * refactor(agents): remove runtime main fallbacks * style(agents): apply roster refactor formatting * refactor(agents): finish roster-only runtime sweep * fix(doctor): migrate legacy main session sqlite * fix(doctor): harden roster session migrations * fix(onboard): commit first agent atomically * fix(config): support empty-roster analysis * fix(agents): preserve legacy main state during creation * fix(setup): materialize baseline agent roster * fix(agents): harden legacy default transfer recovery * fix(agents): simplify roster-only legacy compatibility * fix(agents): preserve staged first-agent entries * fix(config): migrate persisted implicit-main rosters * fix(config): preserve staged empty rosters * fix(agents): finalize roster-only upgrade paths * fix(sessions): close legacy main migration outcomes * fix(config): migrate legacy roster markers at load * fix(sessions): preserve roster upgrade history * refactor(sessions): restore lean legacy main compatibility * fix(setup): prepare first-agent credentials before publish * fix(config): stabilize roster snapshot migration * refactor(sessions): shrink legacy main compatibility * fix(agents): restore roster compatibility fidelity * fix(sessions): preserve divergent legacy history * refactor(agents): narrow roster-only scope * fix(config): isolate roster migration * test(agents): align roster-only fixtures * fix(agents): keep main agent undeletable * fix(agents): harden roster migration invariants * fix(agents): close setup and audit scope gaps * fix(cron): scope session reaper throttles by agent * fix(agents): preserve scoped owner precedence * fix(config): preserve authored config ownership * fix(setup): keep default workspace and roster in sync * fix(setup): preserve default entry workspace on bare runs * fix(agents): adapt roster rebase to keyed entries * fix(agents): honor both roster representations * fix(agents): route roster reads through shared helpers * fix(config): preserve canonical roster writes * fix(cron): resolve dynamic default for session reaper * fix(agents): close dynamic default migration gaps * fix(agents): align scoped session ownership * fix(sessions): preserve legacy main directory casing * fix(agents): align cron and legacy auth ownership * fix(setup): provision the committed default workspace * fix(cron): align scoped ownership and reaping * fix(cron): treat blank agent ids as absent * fix(cron): retain configured session-store owners * fix(agents): repair roster-aware CI boundaries * fix(cron): preserve scoped ownership resolution * fix(agents): preserve rosterless maintenance paths * fix(agents): propagate roster ownership through runtime boundaries * fix(agents): preserve roster ownership across runtime paths * fix(agents): harden roster diagnostics and legacy routing * fix(agents): remove redundant diagnostic import * test(agents): type CLI policy fixture explicitly * fix(config): preserve canonical roster mutation identity * fix(doctor): read canonical agent rosters consistently * fix(config): resolve compound roster unsets safely * fix(config): finalize main-session reconciliation * fix(doctor): read canonical session state safely * fix(sessions): preserve current visibility alias * fix(config): track roster include provenance * test(config): type roster provenance cases * fix(config): refine roster include ownership * fix(agents): preserve staged roster invariants * test(config): align fixtures with explicit roster ownership * test(node-host): preserve optional plan typing * fix(config): preserve authored roster projections * test(config): keep raw roster fixtures explicit * test(config): normalize rosters at runtime fixtures * fix(config): protect authored roster ownership * fix(agents): require explicit session ownership * fix(agents): enforce scoped roster ownership * fix(sessions): merge fixed-store agent partitions * fix(agents): harden roster ownership boundaries * fix(config): reject ambiguous roster projections * fix(sessions): preserve persisted store ownership * fix(sessions): keep collision diagnostics additive * fix(security): scan malformed roster workspaces * test(config): align snapshot fixtures after rebase * test(agents): use explicit roster fixtures * fix(config): harden roster diagnostic boundaries * fix(sessions): isolate fixed-store agent databases * test(agents): type malformed default markers * refactor(sessions): extract store collision resolution * test(system-agent): split oversized setup coverage * style(system-agent): format split setup suite * fix(sessions): preserve promoted store ownership * fix(sessions): derive scoped owner before target * fix(sessions): preserve explicit sqlite ownership * fix(agents): restore roster compatibility across CI * fix(agents): enforce roster-owned runtime boundaries * fix(agents): satisfy default lookup lint * test(sessions): split known-owner coverage * fix(state): satisfy path identity lint * fix(agents): preserve malformed roster safety boundaries * fix(agents): restore roster compatibility at runtime boundaries * fix(config): satisfy roster boundary type checks * fix(agents): preserve roster ownership across runtime probes Setup inference probes now execute as the configured roster owner. Malformed agent-prefixed session rows are intentionally omitted by the fail-closed visibility contract rather than normalized by tests. * fix(agents): satisfy session list owner lint * fix(agents): preserve roster-owned runtime boundaries Restore shared logical rows for exact SQLite session locators while keeping their physical database owner separate. The ownership regression test now constructs an explicit sole-owner database directly instead of relying on first-touch capture, matching the intentional shared-store contract. * fix(sessions): preserve multiply owned exact stores * fix(sessions): restore runtime owner boundaries Keep incognito sentinels agent-owned, fold default-agent approvals into the global snapshot, and preserve the configless legacy-main CLI policy fallback. Also repair the existing CLI watchdog test lifecycle so the compact shard observes its timeout without an unawaited assertion or async timer stall; product behavior is unchanged by that test-only fix. * test(ci): align owner-scoped fixtures These assertions are unchanged. The fixtures now declare the intended non-default runner, expose the session-key constant imported by production status code, and select the main approvals bucket explicitly on Windows. * fix(agents): close final roster ownership gaps
227 lines
6.7 KiB
TypeScript
227 lines
6.7 KiB
TypeScript
/**
|
|
* Resolves default exec tool settings from session and config context.
|
|
*/
|
|
import type { SessionEntry } from "../config/sessions.js";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import {
|
|
loadExecApprovals,
|
|
type ExecAsk,
|
|
type ExecHost,
|
|
type ExecMode,
|
|
type ExecSecurity,
|
|
type ExecTarget,
|
|
maxAsk,
|
|
minSecurity,
|
|
normalizeExecAsk,
|
|
normalizeExecSecurity,
|
|
normalizeExecTarget,
|
|
resolveExecApprovalsFromFile,
|
|
resolveExecModeFromPolicy,
|
|
resolveExecModePolicy,
|
|
} from "../infra/exec-approvals.js";
|
|
import { applyExecPolicyLayer } from "../infra/exec-policy.js";
|
|
import { resolveAgentConfig, resolveSessionAgentId } from "./agent-scope.js";
|
|
import { isRequestedExecTargetAllowed, resolveExecTarget } from "./bash-tools.exec-runtime.js";
|
|
import { resolveSandboxRuntimeStatus } from "./sandbox/runtime-status.js";
|
|
|
|
/** Session-scoped exec fields that may be carried across an isolated runtime boundary. */
|
|
export type ExecSessionDefaults = Pick<
|
|
SessionEntry,
|
|
"execHost" | "execSecurity" | "execAsk" | "execNode" | "execCwd"
|
|
>;
|
|
|
|
// Resolved exec config layers come from global config, agent config, legacy
|
|
// session fields, and per-call overrides.
|
|
type ResolvedExecConfig = {
|
|
host?: ExecTarget;
|
|
mode?: ExecMode;
|
|
security?: ExecSecurity;
|
|
ask?: ExecAsk;
|
|
node?: string;
|
|
};
|
|
|
|
export type ExecPolicyOverrides = Omit<ResolvedExecConfig, "mode">;
|
|
|
|
// Layering keeps the most specific mode/security/ask while preserving policy
|
|
// bounds from approvals and sandbox availability later in resolution.
|
|
type LayeredExecPolicy = {
|
|
mode?: ExecMode;
|
|
security: ExecSecurity;
|
|
ask: ExecAsk;
|
|
};
|
|
|
|
function applySessionLegacyExecPolicyLayer(
|
|
base: LayeredExecPolicy,
|
|
sessionEntry?: ExecSessionDefaults,
|
|
): LayeredExecPolicy {
|
|
const security = normalizeExecSecurity(sessionEntry?.execSecurity);
|
|
const ask = normalizeExecAsk(sessionEntry?.execAsk);
|
|
if (security !== null || ask !== null) {
|
|
return {
|
|
security: security ?? base.security,
|
|
ask: ask ?? base.ask,
|
|
};
|
|
}
|
|
return base;
|
|
}
|
|
|
|
// Gather the shared config state once so exec resolution applies one
|
|
// agent/global/session precedence order.
|
|
function resolveExecConfigState(params: {
|
|
cfg?: OpenClawConfig;
|
|
sessionEntry?: ExecSessionDefaults;
|
|
execOverrides?: ExecPolicyOverrides;
|
|
agentId?: string;
|
|
sessionKey?: string;
|
|
scope?: { kind: "defaults" };
|
|
}): {
|
|
cfg: OpenClawConfig;
|
|
host: ExecTarget;
|
|
agentId: string | undefined;
|
|
agentExec?: ResolvedExecConfig;
|
|
globalExec?: ResolvedExecConfig;
|
|
} {
|
|
const cfg = params.cfg ?? {};
|
|
const resolvedAgentId =
|
|
params.scope?.kind === "defaults"
|
|
? undefined
|
|
: (params.agentId ??
|
|
resolveSessionAgentId({
|
|
sessionKey: params.sessionKey,
|
|
config: cfg,
|
|
}));
|
|
const globalExec = cfg.tools?.exec;
|
|
const agentExec = resolvedAgentId
|
|
? resolveAgentConfig(cfg, resolvedAgentId)?.tools?.exec
|
|
: undefined;
|
|
const host =
|
|
params.execOverrides?.host ??
|
|
normalizeExecTarget(params.sessionEntry?.execHost) ??
|
|
(agentExec?.host as ExecTarget | undefined) ??
|
|
(globalExec?.host as ExecTarget | undefined) ??
|
|
"auto";
|
|
return {
|
|
cfg,
|
|
host,
|
|
agentId: resolvedAgentId,
|
|
agentExec,
|
|
globalExec,
|
|
};
|
|
}
|
|
|
|
/** Resolves whether node exec is usable and any effective node binding. */
|
|
export function resolveNodeExecEligibility(params: {
|
|
cfg?: OpenClawConfig;
|
|
sessionEntry?: ExecSessionDefaults;
|
|
execOverrides?: ExecPolicyOverrides;
|
|
agentId?: string;
|
|
sessionKey?: string;
|
|
sandboxAvailable?: boolean;
|
|
}): { canExec: boolean; node?: string } {
|
|
const defaults = resolveExecDefaults(params);
|
|
const systemRunDenied = params.cfg?.gateway?.nodes?.commands?.deny?.some(
|
|
(command) => command.trim() === "system.run",
|
|
);
|
|
return {
|
|
canExec: defaults.canRequestNode && defaults.security !== "deny" && !systemRunDenied,
|
|
...(defaults.node ? { node: defaults.node } : {}),
|
|
};
|
|
}
|
|
|
|
/** Resolves effective exec host, mode, approval policy, and node availability. */
|
|
export function resolveExecDefaults(params: {
|
|
cfg?: OpenClawConfig;
|
|
sessionEntry?: ExecSessionDefaults;
|
|
execOverrides?: ExecPolicyOverrides;
|
|
agentId?: string;
|
|
sessionKey?: string;
|
|
/** Resolve agents.defaults/tools.exec without applying any roster entry override. */
|
|
scope?: { kind: "defaults" };
|
|
sandboxAvailable?: boolean;
|
|
elevatedRequested?: boolean;
|
|
}): {
|
|
host: ExecTarget;
|
|
effectiveHost: ExecHost;
|
|
mode: ExecMode;
|
|
security: ExecSecurity;
|
|
ask: ExecAsk;
|
|
node?: string;
|
|
canRequestNode: boolean;
|
|
} {
|
|
const {
|
|
cfg,
|
|
host,
|
|
agentId: resolvedAgentId,
|
|
agentExec,
|
|
globalExec,
|
|
} = resolveExecConfigState(params);
|
|
const sandboxAvailable =
|
|
params.sandboxAvailable ??
|
|
(params.sessionKey
|
|
? resolveSandboxRuntimeStatus({
|
|
cfg,
|
|
sessionKey: params.sessionKey,
|
|
}).sandboxed
|
|
: false);
|
|
const resolved = resolveExecTarget({
|
|
configuredTarget: host,
|
|
elevatedRequested: params.elevatedRequested === true,
|
|
sandboxAvailable,
|
|
});
|
|
const defaultSecurity = resolved.effectiveHost === "sandbox" ? "deny" : "full";
|
|
const approvalDefaults =
|
|
resolved.effectiveHost === "sandbox"
|
|
? undefined
|
|
: resolveExecApprovalsFromFile({
|
|
file: loadExecApprovals(),
|
|
agentId: resolvedAgentId,
|
|
overrides: {
|
|
security: defaultSecurity,
|
|
ask: "off",
|
|
},
|
|
}).agent;
|
|
const basePolicy: LayeredExecPolicy = {
|
|
security: approvalDefaults?.security ?? defaultSecurity,
|
|
ask: approvalDefaults?.ask ?? "off",
|
|
};
|
|
const layeredPolicy = applyExecPolicyLayer(
|
|
applySessionLegacyExecPolicyLayer(
|
|
applyExecPolicyLayer(applyExecPolicyLayer(basePolicy, globalExec), agentExec),
|
|
params.sessionEntry,
|
|
),
|
|
params.execOverrides,
|
|
);
|
|
const modePolicy = resolveExecModePolicy(layeredPolicy);
|
|
// Approval files are safety bounds: they can only reduce security/ask from
|
|
// config-derived policy, never grant a less restrictive effective mode.
|
|
const security =
|
|
approvalDefaults?.security !== undefined
|
|
? minSecurity(modePolicy.security, approvalDefaults.security)
|
|
: modePolicy.security;
|
|
const ask =
|
|
approvalDefaults?.ask !== undefined
|
|
? maxAsk(modePolicy.ask, approvalDefaults.ask)
|
|
: modePolicy.ask;
|
|
const mode =
|
|
security === modePolicy.security && ask === modePolicy.ask
|
|
? modePolicy.mode
|
|
: resolveExecModeFromPolicy({ security, ask });
|
|
return {
|
|
host,
|
|
effectiveHost: resolved.effectiveHost,
|
|
mode,
|
|
security,
|
|
ask,
|
|
node:
|
|
params.execOverrides?.node ??
|
|
params.sessionEntry?.execNode ??
|
|
agentExec?.node ??
|
|
globalExec?.node,
|
|
canRequestNode: isRequestedExecTargetAllowed({
|
|
configuredTarget: host,
|
|
requestedTarget: "node",
|
|
sandboxAvailable,
|
|
}),
|
|
};
|
|
}
|