mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
refactor(approvals): unify structured path display
This commit is contained in:
@@ -65,8 +65,8 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
- Gateway/sessions: recover main-agent turns interrupted by a gateway restart from stale transcript-lock evidence, avoiding stuck `status: "running"` sessions without broad post-boot transcript scans. Fixes #70555. Thanks @bitloi.
|
||||
- Plugins/Google Meet: include live Chrome-node readiness in `googlemeet setup` and document the Parallels recovery checks, so stale node tokens or disconnected VM browsers are visible before an agent opens a meeting. Thanks @steipete.
|
||||
- Codex approvals: compact home-directory permission paths to `~` without repeating them as a separate high-risk warning, while preserving filesystem root and wildcard host warnings. Thanks @steipete.
|
||||
- Context engine: keep safeguard compaction checks active after context-engine windowing and for `ownsCompaction` engines, so large transcripts can compact before prompt submission instead of waiting for provider overflow. Fixes #71325. Thanks @steipete.
|
||||
- Approvals: compact structured home-directory paths to `~` across Codex permission prompts and exec approval metadata without repeating them as a separate high-risk warning, while preserving filesystem root and wildcard host warnings. Thanks @steipete.
|
||||
- Plugins/runtime deps: isolate the internal npm cache used for bundled plugin runtime-dependency repair and let package updates refresh/verify already-current installs, so failed update or sudo doctor runs can be repaired by rerunning `openclaw update`. Thanks @steipete.
|
||||
- Agents/delete: keep `--json` output machine-readable and retain workspaces that overlap another agent's workspace instead of moving shared state to Trash. Fixes #70889 and #70890. (#70897) Thanks @kaseonedge.
|
||||
- Browser/screenshot: honor `timeoutMs` through host and node screenshot requests, bound raw CDP screenshot commands, and avoid beyond-viewport CDP capture for ordinary viewport screenshots, so Windows Chrome captures no longer hang past the requested deadline. Fixes #68330. Thanks @Woodylai24.
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
1b8ce6687d91267f78f589ee29d4cca0809fde73ea47c82ddbd14ecf54f1803a plugin-sdk-api-baseline.json
|
||||
55c48203fe5d6409f690f4d27abde41502feec1bfb63d9096cd9958fcf45c2c2 plugin-sdk-api-baseline.jsonl
|
||||
56ccee3ef8ff3b0ba7e2e765ae631b59254464585d5fef9db7e905f2c4c34ded plugin-sdk-api-baseline.json
|
||||
39184cf8afaec691f0352d1a113e30a7099b87c0748237a3c7307e903ba24eee plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -278,7 +278,7 @@ releases.
|
||||
| `plugin-sdk/gateway-runtime` | Gateway helpers | Gateway client and channel-status patch helpers |
|
||||
| `plugin-sdk/config-runtime` | Config helpers | Config load/write helpers |
|
||||
| `plugin-sdk/telegram-command-config` | Telegram command helpers | Fallback-stable Telegram command validation helpers when the bundled Telegram contract surface is unavailable |
|
||||
| `plugin-sdk/approval-runtime` | Approval prompt helpers | Exec/plugin approval payload, approval capability/profile helpers, native approval routing/runtime helpers |
|
||||
| `plugin-sdk/approval-runtime` | Approval prompt helpers | Exec/plugin approval payload, approval capability/profile helpers, native approval routing/runtime helpers, and structured approval display path formatting |
|
||||
| `plugin-sdk/approval-auth-runtime` | Approval auth helpers | Approver resolution, same-chat action auth |
|
||||
| `plugin-sdk/approval-client-runtime` | Approval client helpers | Native exec approval profile/filter helpers |
|
||||
| `plugin-sdk/approval-delivery-runtime` | Approval delivery helpers | Native approval capability/delivery adapters |
|
||||
|
||||
@@ -122,6 +122,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
|
||||
| `plugin-sdk/approval-handler-runtime` | Broader approval handler runtime helpers; prefer the narrower adapter/gateway seams when they are enough |
|
||||
| `plugin-sdk/approval-native-runtime` | Native approval target + account-binding helpers |
|
||||
| `plugin-sdk/approval-reply-runtime` | Exec/plugin approval reply payload helpers |
|
||||
| `plugin-sdk/approval-runtime` | Exec/plugin approval payload helpers, native approval routing/runtime helpers, and structured approval display helpers such as `formatApprovalDisplayPath` |
|
||||
| `plugin-sdk/reply-dedupe` | Narrow inbound reply dedupe reset helpers |
|
||||
| `plugin-sdk/channel-contract-testing` | Narrow channel contract test helpers without the broad testing barrel |
|
||||
| `plugin-sdk/command-auth-native` | Native command auth + native session-target helpers |
|
||||
@@ -156,7 +157,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
|
||||
| `plugin-sdk/config-runtime` | Config load/write helpers and plugin-config lookup helpers |
|
||||
| `plugin-sdk/telegram-command-config` | Telegram command-name/description normalization and duplicate/conflict checks, even when the bundled Telegram contract surface is unavailable |
|
||||
| `plugin-sdk/text-autolink-runtime` | File-reference autolink detection without the broad text-runtime barrel |
|
||||
| `plugin-sdk/approval-runtime` | Exec/plugin approval helpers, approval-capability builders, auth/profile helpers, native routing/runtime helpers |
|
||||
| `plugin-sdk/approval-runtime` | Exec/plugin approval helpers, approval-capability builders, auth/profile helpers, native routing/runtime helpers, and structured approval display path formatting |
|
||||
| `plugin-sdk/reply-runtime` | Shared inbound/reply runtime helpers, chunking, dispatch, heartbeat, reply planner |
|
||||
| `plugin-sdk/reply-dispatch-runtime` | Narrow reply dispatch/finalize and conversation-label helpers |
|
||||
| `plugin-sdk/reply-history` | Shared short-window reply-history helpers such as `buildHistoryContext`, `recordPendingHistoryEntry`, and `clearHistoryEntriesIfEnabled` |
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
type AgentApprovalEventData,
|
||||
formatApprovalDisplayPath,
|
||||
type EmbeddedRunAttemptParams,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import {
|
||||
@@ -431,15 +432,10 @@ function sanitizePermissionHostValue(value: string): string {
|
||||
}
|
||||
|
||||
function sanitizePermissionPathValue(value: string): string {
|
||||
const normalized = sanitizePermissionScalar(value);
|
||||
const homeCompacted = normalized
|
||||
.replace(/^\/home\/(?!\.{1,2}(?=\/|$))[^/]+(?=\/|$)/, "~")
|
||||
.replace(/^\/Users\/(?!\.{1,2}(?=\/|$))[^/]+(?=\/|$)/, "~")
|
||||
.replace(/^[A-Za-z]:[\\/]Users[\\/](?!\.{1,2}(?=[\\/]|$))[^\\/]+(?=[\\/]|$)/i, "~");
|
||||
const displayPath = homeCompacted.startsWith("~")
|
||||
? homeCompacted.replace(/\\/g, "/")
|
||||
: homeCompacted;
|
||||
return truncate(displayPath, PERMISSION_VALUE_MAX_LENGTH);
|
||||
return truncate(
|
||||
formatApprovalDisplayPath(sanitizePermissionScalar(value)),
|
||||
PERMISSION_VALUE_MAX_LENGTH,
|
||||
);
|
||||
}
|
||||
|
||||
function sanitizePermissionScalar(value: string): string {
|
||||
|
||||
27
src/infra/approval-display-paths.test.ts
Normal file
27
src/infra/approval-display-paths.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { formatApprovalDisplayPath } from "./approval-display-paths.js";
|
||||
|
||||
describe("approval display paths", () => {
|
||||
it.each([
|
||||
["/home/alice", "~"],
|
||||
["/home/alice/.ssh/id_rsa", "~/.ssh/id_rsa"],
|
||||
["/Users/alice/Documents/project", "~/Documents/project"],
|
||||
["C:/Users/alice/project", "~/project"],
|
||||
["c:/users/bob/project", "~/project"],
|
||||
["C:\\Users\\alice\\.ssh\\id_rsa", "~/.ssh/id_rsa"],
|
||||
["D:\\Users\\alice\\Downloads\\file.txt", "~/Downloads/file.txt"],
|
||||
["/workspace/project", "/workspace/project"],
|
||||
["C:\\workspace\\project", "C:\\workspace\\project"],
|
||||
])("formats %s as %s", (input, expected) => {
|
||||
expect(formatApprovalDisplayPath(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it.each([
|
||||
"/Users/alice/../Library",
|
||||
"/home/alice/./project",
|
||||
"C:/Users/alice/../Windows/System32",
|
||||
"C:\\Users\\alice\\.\\project",
|
||||
])("does not compact relative-segment path %s", (input) => {
|
||||
expect(formatApprovalDisplayPath(input)).toBe(input);
|
||||
});
|
||||
});
|
||||
30
src/infra/approval-display-paths.ts
Normal file
30
src/infra/approval-display-paths.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export function formatApprovalDisplayPath(value: string): string {
|
||||
const normalized = value.trim();
|
||||
if (!normalized || hasRelativePathSegment(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
const unixHomeMatch = normalized.match(/^\/(?:home|Users)\/([^/]+)(.*)$/);
|
||||
if (unixHomeMatch && isSafeHomeSegment(unixHomeMatch[1])) {
|
||||
return compactHomeSuffix(unixHomeMatch[2] ?? "");
|
||||
}
|
||||
|
||||
const windowsHomeMatch = normalized.match(/^[A-Za-z]:[\\/]Users[\\/]([^\\/]+)(.*)$/i);
|
||||
if (windowsHomeMatch && isSafeHomeSegment(windowsHomeMatch[1])) {
|
||||
return compactHomeSuffix(windowsHomeMatch[2] ?? "");
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function compactHomeSuffix(suffix: string): string {
|
||||
return `~${suffix.replace(/\\/g, "/")}`;
|
||||
}
|
||||
|
||||
function isSafeHomeSegment(segment: string | undefined): boolean {
|
||||
return segment !== undefined && segment !== "." && segment !== "..";
|
||||
}
|
||||
|
||||
function hasRelativePathSegment(value: string): boolean {
|
||||
return /(^|[\\/])\.{1,2}(?=[\\/]|$)/.test(value);
|
||||
}
|
||||
@@ -289,6 +289,19 @@ describe("exec approval reply helpers", () => {
|
||||
expect(payload.text).toContain("Full id: `req-1`");
|
||||
});
|
||||
|
||||
it("compacts structured cwd paths in pending reply payloads", () => {
|
||||
const payload = buildExecApprovalPendingReplyPayload({
|
||||
approvalId: "req-home",
|
||||
approvalSlug: "slug-home",
|
||||
command: "pwd",
|
||||
cwd: "C:\\Users\\alice\\project",
|
||||
host: "gateway",
|
||||
});
|
||||
|
||||
expect(payload.text).toContain("CWD: ~/project");
|
||||
expect(payload.text).not.toContain("C:\\Users\\alice");
|
||||
});
|
||||
|
||||
it("omits allow-always actions when the effective policy requires approval every time", () => {
|
||||
const payload = buildExecApprovalPendingReplyPayload({
|
||||
approvalId: "req-ask-always",
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { formatApprovalDisplayPath } from "./approval-display-paths.js";
|
||||
import {
|
||||
describeNativeExecApprovalClientSetup,
|
||||
listNativeExecApprovalClientLabels,
|
||||
@@ -322,7 +323,7 @@ export function buildExecApprovalPendingReplyPayload(
|
||||
info.push(`Node: ${params.nodeId}`);
|
||||
}
|
||||
if (params.cwd) {
|
||||
info.push(`CWD: ${params.cwd}`);
|
||||
info.push(`CWD: ${formatApprovalDisplayPath(params.cwd)}`);
|
||||
}
|
||||
if (typeof params.expiresAtMs === "number" && Number.isFinite(params.expiresAtMs)) {
|
||||
info.push(
|
||||
|
||||
@@ -55,6 +55,7 @@ export type {
|
||||
|
||||
export { VERSION as OPENCLAW_VERSION } from "../version.js";
|
||||
export { formatErrorMessage } from "../infra/errors.js";
|
||||
export { formatApprovalDisplayPath } from "../infra/approval-display-paths.js";
|
||||
export { emitAgentEvent } from "../infra/agent-events.js";
|
||||
export { log as embeddedAgentLog } from "../agents/pi-embedded-runner/logger.js";
|
||||
export { resolveEmbeddedAgentRuntime } from "../agents/pi-embedded-runner/runtime.js";
|
||||
|
||||
@@ -19,6 +19,7 @@ export {
|
||||
type ExecApprovalReplyMetadata,
|
||||
} from "../infra/exec-approval-reply.js";
|
||||
export { resolveExecApprovalCommandDisplay } from "../infra/exec-approval-command-display.js";
|
||||
export { formatApprovalDisplayPath } from "../infra/approval-display-paths.js";
|
||||
export {
|
||||
createChannelApproverDmTargetResolver,
|
||||
createChannelNativeOriginTargetResolver,
|
||||
|
||||
@@ -42,6 +42,7 @@ export * from "../infra/exec-approval-session-target.ts";
|
||||
export * from "../infra/exec-approvals.ts";
|
||||
export * from "../infra/approval-native-delivery.ts";
|
||||
export * from "../infra/approval-native-runtime.ts";
|
||||
export * from "../infra/approval-display-paths.ts";
|
||||
export * from "../infra/plugin-approvals.ts";
|
||||
export * from "../infra/fetch.js";
|
||||
export * from "../infra/file-lock.js";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { html, nothing } from "lit";
|
||||
import { formatApprovalDisplayPath } from "../../../../src/infra/approval-display-paths.ts";
|
||||
import type { AppViewState } from "../app-view-state.ts";
|
||||
import type {
|
||||
ExecApprovalRequest,
|
||||
@@ -19,11 +20,14 @@ function formatRemaining(ms: number): string {
|
||||
return `${hours}h`;
|
||||
}
|
||||
|
||||
function renderMetaRow(label: string, value?: string | null) {
|
||||
function renderMetaRow(label: string, value?: string | null, opts?: { path?: boolean }) {
|
||||
if (!value) {
|
||||
return nothing;
|
||||
}
|
||||
return html`<div class="exec-approval-meta-row"><span>${label}</span><span>${value}</span></div>`;
|
||||
const displayValue = opts?.path ? formatApprovalDisplayPath(value) : value;
|
||||
return html`<div class="exec-approval-meta-row">
|
||||
<span>${label}</span><span>${displayValue}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderExecBody(request: ExecApprovalRequestPayload) {
|
||||
@@ -31,8 +35,11 @@ function renderExecBody(request: ExecApprovalRequestPayload) {
|
||||
<div class="exec-approval-command mono">${request.command}</div>
|
||||
<div class="exec-approval-meta">
|
||||
${renderMetaRow("Host", request.host)} ${renderMetaRow("Agent", request.agentId)}
|
||||
${renderMetaRow("Session", request.sessionKey)} ${renderMetaRow("CWD", request.cwd)}
|
||||
${renderMetaRow("Resolved", request.resolvedPath)}
|
||||
${renderMetaRow("Session", request.sessionKey)}
|
||||
${renderMetaRow("CWD", request.cwd, {
|
||||
path: true,
|
||||
})}
|
||||
${renderMetaRow("Resolved", request.resolvedPath, { path: true })}
|
||||
${renderMetaRow("Security", request.security)} ${renderMetaRow("Ask", request.ask)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user