Compare commits
3 Commits
main
...
fix/launch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43e649d919 | ||
|
|
1a74d50578 | ||
|
|
30990d73b2 |
@ -57,6 +57,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Telegram/DM routing: dedupe inbound Telegram DMs per agent instead of per session key so the same DM cannot trigger duplicate replies when both `agent:main:main` and `agent:main:telegram:direct:<id>` resolve for one agent. Fixes #40005. Supersedes #40116. (#40519) thanks @obviyus.
|
||||
- Matrix/DM routing: add safer fallback detection for broken `m.direct` homeservers, honor explicit room bindings over DM classification, and preserve room-bound agent selection for Matrix DM rooms. (#19736) Thanks @derbronko.
|
||||
- Cron/Telegram announce delivery: route text-only announce jobs through the real outbound adapters after finalizing descendant output so plain Telegram targets no longer report `delivered: true` when no message actually reached Telegram. (#40575) thanks @obviyus.
|
||||
- macOS/launchd restart recovery: call `launchctl enable` before `bootstrap` in LaunchAgent restart and repair flows so persisted disabled-state jobs reload cleanly after `openclaw gateway restart`. Landed from contributor PR #39237 by @scoootscooob. Thanks @scoootscooob.
|
||||
|
||||
## 2026.3.7
|
||||
|
||||
|
||||
@ -156,7 +156,7 @@ describe("launchctl list detection", () => {
|
||||
});
|
||||
|
||||
describe("launchd bootstrap repair", () => {
|
||||
it("bootstraps and kickstarts the resolved label", async () => {
|
||||
it("enables, bootstraps, and kickstarts the resolved label", async () => {
|
||||
const env: Record<string, string | undefined> = {
|
||||
HOME: "/Users/test",
|
||||
OPENCLAW_PROFILE: "default",
|
||||
@ -167,9 +167,23 @@ describe("launchd bootstrap repair", () => {
|
||||
const domain = typeof process.getuid === "function" ? `gui/${process.getuid()}` : "gui/501";
|
||||
const label = "ai.openclaw.gateway";
|
||||
const plistPath = resolveLaunchAgentPlistPath(env);
|
||||
const serviceId = `${domain}/${label}`;
|
||||
|
||||
expect(state.launchctlCalls).toContainEqual(["bootstrap", domain, plistPath]);
|
||||
expect(state.launchctlCalls).toContainEqual(["kickstart", "-k", `${domain}/${label}`]);
|
||||
const enableIndex = state.launchctlCalls.findIndex(
|
||||
(c) => c[0] === "enable" && c[1] === serviceId,
|
||||
);
|
||||
const bootstrapIndex = state.launchctlCalls.findIndex(
|
||||
(c) => c[0] === "bootstrap" && c[1] === domain && c[2] === plistPath,
|
||||
);
|
||||
const kickstartIndex = state.launchctlCalls.findIndex(
|
||||
(c) => c[0] === "kickstart" && c[1] === "-k" && c[2] === serviceId,
|
||||
);
|
||||
|
||||
expect(enableIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(bootstrapIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(kickstartIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(enableIndex).toBeLessThan(bootstrapIndex);
|
||||
expect(bootstrapIndex).toBeLessThan(kickstartIndex);
|
||||
});
|
||||
});
|
||||
|
||||
@ -241,7 +255,7 @@ describe("launchd install", () => {
|
||||
expect(plist).toContain(`<integer>${LAUNCH_AGENT_THROTTLE_INTERVAL_SECONDS}</integer>`);
|
||||
});
|
||||
|
||||
it("restarts LaunchAgent with bootout-bootstrap-kickstart order", async () => {
|
||||
it("restarts LaunchAgent with bootout-enable-bootstrap-kickstart order", async () => {
|
||||
const env = createDefaultLaunchdEnv();
|
||||
await restartLaunchAgent({
|
||||
env,
|
||||
@ -251,20 +265,26 @@ describe("launchd install", () => {
|
||||
const domain = typeof process.getuid === "function" ? `gui/${process.getuid()}` : "gui/501";
|
||||
const label = "ai.openclaw.gateway";
|
||||
const plistPath = resolveLaunchAgentPlistPath(env);
|
||||
const serviceId = `${domain}/${label}`;
|
||||
const bootoutIndex = state.launchctlCalls.findIndex(
|
||||
(c) => c[0] === "bootout" && c[1] === `${domain}/${label}`,
|
||||
(c) => c[0] === "bootout" && c[1] === serviceId,
|
||||
);
|
||||
const enableIndex = state.launchctlCalls.findIndex(
|
||||
(c) => c[0] === "enable" && c[1] === serviceId,
|
||||
);
|
||||
const bootstrapIndex = state.launchctlCalls.findIndex(
|
||||
(c) => c[0] === "bootstrap" && c[1] === domain && c[2] === plistPath,
|
||||
);
|
||||
const kickstartIndex = state.launchctlCalls.findIndex(
|
||||
(c) => c[0] === "kickstart" && c[1] === "-k" && c[2] === `${domain}/${label}`,
|
||||
(c) => c[0] === "kickstart" && c[1] === "-k" && c[2] === serviceId,
|
||||
);
|
||||
|
||||
expect(bootoutIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(enableIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(bootstrapIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(kickstartIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(bootoutIndex).toBeLessThan(bootstrapIndex);
|
||||
expect(bootoutIndex).toBeLessThan(enableIndex);
|
||||
expect(enableIndex).toBeLessThan(bootstrapIndex);
|
||||
expect(bootstrapIndex).toBeLessThan(kickstartIndex);
|
||||
});
|
||||
|
||||
|
||||
@ -207,6 +207,9 @@ export async function repairLaunchAgentBootstrap(args: {
|
||||
const domain = resolveGuiDomain();
|
||||
const label = resolveLaunchAgentLabel({ env });
|
||||
const plistPath = resolveLaunchAgentPlistPath(env);
|
||||
// launchd can persist "disabled" state after bootout; clear it before bootstrap
|
||||
// (matches the same guard in installLaunchAgent and restartLaunchAgent).
|
||||
await execLaunchctl(["enable", `${domain}/${label}`]);
|
||||
const boot = await execLaunchctl(["bootstrap", domain, plistPath]);
|
||||
if (boot.code !== 0) {
|
||||
return { ok: false, detail: (boot.stderr || boot.stdout).trim() || undefined };
|
||||
@ -466,6 +469,9 @@ export async function restartLaunchAgent({
|
||||
await waitForPidExit(previousPid);
|
||||
}
|
||||
|
||||
// launchd can persist "disabled" state after bootout; clear it before bootstrap
|
||||
// (matches the same guard in installLaunchAgent).
|
||||
await execLaunchctl(["enable", `${domain}/${label}`]);
|
||||
const boot = await execLaunchctl(["bootstrap", domain, plistPath]);
|
||||
if (boot.code !== 0) {
|
||||
const detail = (boot.stderr || boot.stdout).trim();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user