mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
docs: document agent command exec helpers
This commit is contained in:
@@ -1,17 +1,20 @@
|
||||
import type { AgentMessage } from "../runtime/index.js";
|
||||
|
||||
/** Mutable lifecycle flags observed while a single agent attempt runs. */
|
||||
export type AgentAttemptLifecycleState = {
|
||||
currentTurnUserMessagePersisted: boolean;
|
||||
lifecycleFinishing: boolean;
|
||||
lifecycleEnded: boolean;
|
||||
};
|
||||
|
||||
/** Event shape emitted by runtimes during an agent attempt. */
|
||||
export type AgentAttemptLifecycleEvent = {
|
||||
stream: string;
|
||||
data?: Record<string, unknown>;
|
||||
sessionKey?: string;
|
||||
};
|
||||
|
||||
/** Creates callbacks that update lifecycle flags for persistence decisions. */
|
||||
export function createAgentAttemptLifecycleCallbacks(state: AgentAttemptLifecycleState): {
|
||||
onUserMessagePersisted: (message: Extract<AgentMessage, { role: "user" }>) => void;
|
||||
onAgentEvent: (evt: AgentAttemptLifecycleEvent) => void;
|
||||
@@ -24,6 +27,8 @@ export function createAgentAttemptLifecycleCallbacks(state: AgentAttemptLifecycl
|
||||
if (evt.stream !== "lifecycle" || typeof evt.data?.phase !== "string") {
|
||||
return;
|
||||
}
|
||||
// Finishing means output ended but transcript/session persistence may still
|
||||
// need to run; end/error means the runtime lifecycle is complete.
|
||||
if (evt.data.phase === "finishing") {
|
||||
state.lifecycleFinishing = true;
|
||||
return;
|
||||
|
||||
@@ -6,6 +6,8 @@ import { normalizeOptionalString } from "@openclaw/normalization-core/string-coe
|
||||
const CLAUDE_PROJECTS_DIRNAME = path.join(".claude", "projects");
|
||||
const MAX_SANITIZED_PROJECT_LENGTH = 200;
|
||||
|
||||
// Claude CLI stores project state under a sanitized workspace key. Add a stable
|
||||
// hash when the key is truncated so long paths do not collide silently.
|
||||
function simpleHash36(input: string): string {
|
||||
let hash = 0;
|
||||
for (let index = 0; index < input.length; index += 1) {
|
||||
@@ -22,6 +24,8 @@ function sanitizeClaudeCliProjectKey(workspaceDir: string): string {
|
||||
return `${sanitized.slice(0, MAX_SANITIZED_PROJECT_LENGTH)}-${simpleHash36(workspaceDir)}`;
|
||||
}
|
||||
|
||||
// Realpath when possible so symlinked workspaces reuse the same Claude project
|
||||
// directory as their canonical path.
|
||||
function canonicalizeWorkspaceDir(workspaceDir: string): string {
|
||||
const resolved = path.resolve(workspaceDir).normalize("NFC");
|
||||
try {
|
||||
@@ -31,6 +35,7 @@ function canonicalizeWorkspaceDir(workspaceDir: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolves Claude CLI's per-workspace project directory. */
|
||||
export function resolveClaudeCliProjectDirForWorkspace(params: {
|
||||
workspaceDir: string;
|
||||
homeDir?: string;
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
// Runtime barrel for session-store writes; keeps command modules from importing
|
||||
// config/session persistence until an agent run needs to save state.
|
||||
export { updateSessionStoreAfterAgentRun } from "./session-store.js";
|
||||
export { loadSessionStore } from "../../config/sessions.js";
|
||||
|
||||
@@ -21,6 +21,8 @@ import { resolveAgentConfig, resolveSessionAgentId } from "./agent-scope.js";
|
||||
import { isRequestedExecTargetAllowed, resolveExecTarget } from "./bash-tools.exec-runtime.js";
|
||||
import { resolveSandboxRuntimeStatus } from "./sandbox/runtime-status.js";
|
||||
|
||||
// Resolved exec config layers come from global config, agent config, legacy
|
||||
// session fields, and per-call overrides.
|
||||
type ResolvedExecConfig = {
|
||||
host?: ExecTarget;
|
||||
mode?: ExecMode;
|
||||
@@ -31,10 +33,14 @@ type ResolvedExecConfig = {
|
||||
|
||||
type ExecOverridesConfig = Omit<ResolvedExecConfig, "mode">;
|
||||
|
||||
// Legacy security/ask values remain accepted on existing sessions/config, but
|
||||
// mode wins when present because it expands to a complete policy tuple.
|
||||
function hasLegacyExecPolicyOverride(exec?: ResolvedExecConfig): boolean {
|
||||
return exec?.security !== undefined || exec?.ask !== undefined;
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -78,6 +84,8 @@ function applySessionLegacyExecPolicyLayer(
|
||||
return base;
|
||||
}
|
||||
|
||||
// Gather the shared config state once so canExecRequestNode and
|
||||
// resolveExecDefaults stay aligned on agent/global/session precedence.
|
||||
function resolveExecConfigState(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
sessionEntry?: SessionEntry;
|
||||
@@ -133,6 +141,7 @@ function resolveExecSandboxAvailability(params: {
|
||||
);
|
||||
}
|
||||
|
||||
/** Returns whether the current exec policy allows requesting host node execution. */
|
||||
export function canExecRequestNode(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
sessionEntry?: SessionEntry;
|
||||
@@ -153,6 +162,7 @@ export function canExecRequestNode(params: {
|
||||
});
|
||||
}
|
||||
|
||||
/** Resolves effective exec host, mode, approval policy, and node availability. */
|
||||
export function resolveExecDefaults(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
sessionEntry?: SessionEntry;
|
||||
@@ -211,6 +221,8 @@ export function resolveExecDefaults(params: {
|
||||
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)
|
||||
|
||||
@@ -2,11 +2,13 @@ import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
|
||||
/** Resolves the private transcript path for an internal session-effect run. */
|
||||
export function resolveInternalSessionEffectsTranscriptPath(runId: string): string {
|
||||
const safeRunId = runId.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 120) || "run";
|
||||
return path.join(resolveStateDir(), "internal-agent-runs", `${safeRunId}.jsonl`);
|
||||
}
|
||||
|
||||
/** Copies or creates a private transcript for internal session-effect recovery. */
|
||||
export async function prepareInternalSessionEffectsTranscript(params: {
|
||||
sessionFile?: string;
|
||||
runId: string;
|
||||
@@ -34,6 +36,7 @@ export async function prepareInternalSessionEffectsTranscript(params: {
|
||||
return sessionFile;
|
||||
}
|
||||
|
||||
/** Removes an internal session-effect transcript if it is inside the owned dir. */
|
||||
export async function removeInternalSessionEffectsTranscript(
|
||||
sessionFile: string | undefined,
|
||||
): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user