File size: 4,082 Bytes
fc93158 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | import { randomUUID } from "node:crypto";
import type { CliDeps } from "../../cli/deps.js";
import { loadConfig, type OpenClawConfig } from "../../config/config.js";
import { resolveMainSessionKeyFromConfig } from "../../config/sessions.js";
import { runCronIsolatedAgentTurn } from "../../cron/isolated-agent.js";
import type { CronJob } from "../../cron/types.js";
import { requestHeartbeatNow } from "../../infra/heartbeat-wake.js";
import { enqueueSystemEvent } from "../../infra/system-events.js";
import type { createSubsystemLogger } from "../../logging/subsystem.js";
import {
normalizeHookDispatchSessionKey,
type HookAgentDispatchPayload,
type HooksConfigResolved,
} from "../hooks.js";
import { createHooksRequestHandler, type HookClientIpConfig } from "../server-http.js";
type SubsystemLogger = ReturnType<typeof createSubsystemLogger>;
export function resolveHookClientIpConfig(cfg: OpenClawConfig): HookClientIpConfig {
return {
trustedProxies: cfg.gateway?.trustedProxies,
allowRealIpFallback: cfg.gateway?.allowRealIpFallback === true,
};
}
export function createGatewayHooksRequestHandler(params: {
deps: CliDeps;
getHooksConfig: () => HooksConfigResolved | null;
getClientIpConfig: () => HookClientIpConfig;
bindHost: string;
port: number;
logHooks: SubsystemLogger;
}) {
const { deps, getHooksConfig, getClientIpConfig, bindHost, port, logHooks } = params;
const dispatchWakeHook = (value: { text: string; mode: "now" | "next-heartbeat" }) => {
const sessionKey = resolveMainSessionKeyFromConfig();
enqueueSystemEvent(value.text, { sessionKey });
if (value.mode === "now") {
requestHeartbeatNow({ reason: "hook:wake" });
}
};
const dispatchAgentHook = (value: HookAgentDispatchPayload) => {
const sessionKey = normalizeHookDispatchSessionKey({
sessionKey: value.sessionKey,
targetAgentId: value.agentId,
});
const mainSessionKey = resolveMainSessionKeyFromConfig();
const jobId = randomUUID();
const now = Date.now();
const job: CronJob = {
id: jobId,
agentId: value.agentId,
name: value.name,
enabled: true,
createdAtMs: now,
updatedAtMs: now,
schedule: { kind: "at", at: new Date(now).toISOString() },
sessionTarget: "isolated",
wakeMode: value.wakeMode,
payload: {
kind: "agentTurn",
message: value.message,
model: value.model,
thinking: value.thinking,
timeoutSeconds: value.timeoutSeconds,
deliver: value.deliver,
channel: value.channel,
to: value.to,
allowUnsafeExternalContent: value.allowUnsafeExternalContent,
},
state: { nextRunAtMs: now },
};
const runId = randomUUID();
void (async () => {
try {
const cfg = loadConfig();
const result = await runCronIsolatedAgentTurn({
cfg,
deps,
job,
message: value.message,
sessionKey,
lane: "cron",
deliveryContract: "shared",
});
const summary = result.summary?.trim() || result.error?.trim() || result.status;
const prefix =
result.status === "ok" ? `Hook ${value.name}` : `Hook ${value.name} (${result.status})`;
if (!result.delivered) {
enqueueSystemEvent(`${prefix}: ${summary}`.trim(), {
sessionKey: mainSessionKey,
});
if (value.wakeMode === "now") {
requestHeartbeatNow({ reason: `hook:${jobId}` });
}
}
} catch (err) {
logHooks.warn(`hook agent failed: ${String(err)}`);
enqueueSystemEvent(`Hook ${value.name} (error): ${String(err)}`, {
sessionKey: mainSessionKey,
});
if (value.wakeMode === "now") {
requestHeartbeatNow({ reason: `hook:${jobId}:error` });
}
}
})();
return runId;
};
return createHooksRequestHandler({
getHooksConfig,
bindHost,
port,
logHooks,
getClientIpConfig,
dispatchAgentHook,
dispatchWakeHook,
});
}
|