mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
fix(node): default mac headless system.run to local host
Co-authored-by: aethnova <262512133+aethnova@users.noreply.github.com>
This commit is contained in:
@@ -35,6 +35,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/Media sandbox: propagate trusted `mediaLocalRoots` through plugin action dispatch (including Discord/Telegram action adapters) so plugin send paths enforce the same agent-scoped local-media sandbox roots as core outbound sends. (#20258, #22718)
|
||||
- Agents/Workspace guard: map sandbox container-workdir file-tool paths (for example `/workspace/...` and `file:///workspace/...`) to host workspace roots before workspace-only validation, preventing false `Path escapes sandbox root` rejections for sandbox file tools. (#9560)
|
||||
- Gateway/Exec approvals: expire approval requests immediately when no approval-capable gateway clients are connected and no forwarding targets are available, avoiding delayed approvals after restarts/offline approver windows. (#22144)
|
||||
- Node/macOS exec host: default headless macOS node `system.run` to local execution and only route through the companion app when `OPENCLAW_NODE_EXEC_HOST=app` is explicitly set, avoiding companion-app filesystem namespace mismatches during exec. (#23547)
|
||||
- Slack/Threading: sessions: keep parent-session forking and thread-history context active beyond first turn by removing first-turn-only gates in session init, thread-history fetch, and reply prompt context injection. (#23843, #23090) Thanks @vincentkoc and @Taskle.
|
||||
- Slack/Threading: respect `replyToMode` when Slack auto-populates top-level `thread_ts`, and ignore inline `replyToId` directive tags when `replyToMode` is `off` so thread forcing stays disabled unless explicitly configured. (#23839, #23320, #23513) Thanks @vincentkoc and @dorukardahan.
|
||||
- Slack/Extension: forward `message read` `threadId` to `readMessages` and use delivery-context `threadId` as outbound `thread_ts` fallback so extension replies/reads stay in the correct Slack thread. (#22216, #22485, #23836) Thanks @vincentkoc, @lan17 and @dorukardahan.
|
||||
|
||||
@@ -333,9 +333,9 @@ Notes:
|
||||
- The node host stores its node id, token, display name, and gateway connection info in `~/.openclaw/node.json`.
|
||||
- Exec approvals are enforced locally via `~/.openclaw/exec-approvals.json`
|
||||
(see [Exec approvals](/tools/exec-approvals)).
|
||||
- On macOS, the headless node host prefers the companion app exec host when reachable and falls
|
||||
back to local execution if the app is unavailable. Set `OPENCLAW_NODE_EXEC_HOST=app` to require
|
||||
the app, or `OPENCLAW_NODE_EXEC_FALLBACK=0` to disable fallback.
|
||||
- On macOS, the headless node host executes `system.run` locally by default. Set
|
||||
`OPENCLAW_NODE_EXEC_HOST=app` to route `system.run` through the companion app exec host; add
|
||||
`OPENCLAW_NODE_EXEC_FALLBACK=0` to require the app host and fail closed if it is unavailable.
|
||||
- Add `--tls` / `--tls-fingerprint` when the Gateway WS uses TLS.
|
||||
|
||||
## Mac node mode
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { formatSystemRunAllowlistMissMessage } from "./invoke-system-run.js";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { ExecHostResponse } from "../infra/exec-host.js";
|
||||
import { handleSystemRunInvoke, formatSystemRunAllowlistMissMessage } from "./invoke-system-run.js";
|
||||
|
||||
describe("formatSystemRunAllowlistMissMessage", () => {
|
||||
it("returns legacy allowlist miss message by default", () => {
|
||||
@@ -14,3 +15,102 @@ describe("formatSystemRunAllowlistMissMessage", () => {
|
||||
).toContain("Windows shell wrappers like cmd.exe /c require approval");
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleSystemRunInvoke mac app exec host routing", () => {
|
||||
async function runSystemInvoke(params: {
|
||||
preferMacAppExecHost: boolean;
|
||||
runViaResponse?: ExecHostResponse | null;
|
||||
}) {
|
||||
const runCommand = vi.fn(async () => ({
|
||||
success: true,
|
||||
stdout: "local-ok",
|
||||
stderr: "",
|
||||
timedOut: false,
|
||||
truncated: false,
|
||||
exitCode: 0,
|
||||
error: null,
|
||||
}));
|
||||
const runViaMacAppExecHost = vi.fn(async () => params.runViaResponse ?? null);
|
||||
const sendInvokeResult = vi.fn(async () => {});
|
||||
const sendExecFinishedEvent = vi.fn(async () => {});
|
||||
|
||||
await handleSystemRunInvoke({
|
||||
client: {} as never,
|
||||
params: {
|
||||
command: ["echo", "ok"],
|
||||
approved: true,
|
||||
sessionKey: "agent:main:main",
|
||||
},
|
||||
skillBins: {
|
||||
current: async () => new Set<string>(),
|
||||
},
|
||||
execHostEnforced: false,
|
||||
execHostFallbackAllowed: true,
|
||||
resolveExecSecurity: () => "full",
|
||||
resolveExecAsk: () => "off",
|
||||
isCmdExeInvocation: () => false,
|
||||
sanitizeEnv: () => undefined,
|
||||
runCommand,
|
||||
runViaMacAppExecHost,
|
||||
sendNodeEvent: async () => {},
|
||||
buildExecEventPayload: (payload) => payload,
|
||||
sendInvokeResult,
|
||||
sendExecFinishedEvent,
|
||||
preferMacAppExecHost: params.preferMacAppExecHost,
|
||||
});
|
||||
|
||||
return { runCommand, runViaMacAppExecHost, sendInvokeResult, sendExecFinishedEvent };
|
||||
}
|
||||
|
||||
it("uses local execution by default when mac app exec host preference is disabled", async () => {
|
||||
const { runCommand, runViaMacAppExecHost, sendInvokeResult } = await runSystemInvoke({
|
||||
preferMacAppExecHost: false,
|
||||
});
|
||||
|
||||
expect(runViaMacAppExecHost).not.toHaveBeenCalled();
|
||||
expect(runCommand).toHaveBeenCalledTimes(1);
|
||||
expect(sendInvokeResult).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
ok: true,
|
||||
payloadJSON: expect.stringContaining("local-ok"),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses mac app exec host when explicitly preferred", async () => {
|
||||
const { runCommand, runViaMacAppExecHost, sendInvokeResult } = await runSystemInvoke({
|
||||
preferMacAppExecHost: true,
|
||||
runViaResponse: {
|
||||
ok: true,
|
||||
payload: {
|
||||
success: true,
|
||||
stdout: "app-ok",
|
||||
stderr: "",
|
||||
timedOut: false,
|
||||
truncated: false,
|
||||
exitCode: 0,
|
||||
error: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(runViaMacAppExecHost).toHaveBeenCalledWith({
|
||||
approvals: expect.objectContaining({
|
||||
agent: expect.objectContaining({
|
||||
security: "full",
|
||||
ask: "off",
|
||||
}),
|
||||
}),
|
||||
request: expect.objectContaining({
|
||||
command: ["echo", "ok"],
|
||||
}),
|
||||
});
|
||||
expect(runCommand).not.toHaveBeenCalled();
|
||||
expect(sendInvokeResult).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
ok: true,
|
||||
payloadJSON: expect.stringContaining("app-ok"),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,6 +70,7 @@ export async function handleSystemRunInvoke(opts: {
|
||||
success?: boolean;
|
||||
};
|
||||
}) => Promise<void>;
|
||||
preferMacAppExecHost: boolean;
|
||||
}): Promise<void> {
|
||||
const command = resolveSystemRunCommand({
|
||||
command: opts.params.command,
|
||||
@@ -166,7 +167,7 @@ export async function handleSystemRunInvoke(opts: {
|
||||
? opts.isCmdExeInvocation(segments[0]?.argv ?? [])
|
||||
: opts.isCmdExeInvocation(argv);
|
||||
|
||||
const useMacAppExec = process.platform === "darwin";
|
||||
const useMacAppExec = opts.preferMacAppExecHost;
|
||||
if (useMacAppExec) {
|
||||
const execRequest: ExecHostRequest = {
|
||||
command: argv,
|
||||
|
||||
@@ -35,6 +35,7 @@ const DEFAULT_NODE_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sb
|
||||
const execHostEnforced = process.env.OPENCLAW_NODE_EXEC_HOST?.trim().toLowerCase() === "app";
|
||||
const execHostFallbackAllowed =
|
||||
process.env.OPENCLAW_NODE_EXEC_FALLBACK?.trim().toLowerCase() !== "0";
|
||||
const preferMacAppExecHost = process.platform === "darwin" && execHostEnforced;
|
||||
|
||||
type SystemWhichParams = {
|
||||
bins: string[];
|
||||
@@ -457,6 +458,7 @@ export async function handleInvoke(
|
||||
sendExecFinishedEvent: async ({ sessionKey, runId, cmdText, result }) => {
|
||||
await sendExecFinishedEvent({ client, sessionKey, runId, cmdText, result });
|
||||
},
|
||||
preferMacAppExecHost,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user