Expose cron jobId in plugin hook context

This commit is contained in:
Scott Glover
2026-04-25 19:08:58 -05:00
committed by Peter Steinberger
parent 264d6f6aef
commit 371b69b3e2
10 changed files with 34 additions and 3 deletions

View File

@@ -108,12 +108,13 @@ describe("runCliAgent cron before_agent_reply seam", () => {
reply: { text: "dreaming claimed via cli runner" }, reply: { text: "dreaming claimed via cli runner" },
}); });
const result = await runCliAgent({ ...baseRunParams, trigger: "cron" }); const result = await runCliAgent({ ...baseRunParams, trigger: "cron", jobId: "cron-job-123" });
expect(runBeforeAgentReplyMock).toHaveBeenCalledTimes(1); expect(runBeforeAgentReplyMock).toHaveBeenCalledTimes(1);
expect(runBeforeAgentReplyMock).toHaveBeenCalledWith( expect(runBeforeAgentReplyMock).toHaveBeenCalledWith(
{ cleanedBody: baseRunParams.prompt }, { cleanedBody: baseRunParams.prompt },
expect.objectContaining({ expect.objectContaining({
jobId: "cron-job-123",
agentId: baseRunParams.agentId, agentId: baseRunParams.agentId,
sessionId: baseRunParams.sessionId, sessionId: baseRunParams.sessionId,
sessionKey: baseRunParams.sessionKey, sessionKey: baseRunParams.sessionKey,
@@ -133,7 +134,7 @@ describe("runCliAgent cron before_agent_reply seam", () => {
hasHooksMock.mockImplementation((hookName) => hookName === "before_agent_reply"); hasHooksMock.mockImplementation((hookName) => hookName === "before_agent_reply");
runBeforeAgentReplyMock.mockResolvedValue({ handled: true }); runBeforeAgentReplyMock.mockResolvedValue({ handled: true });
await runCliAgent({ ...baseRunParams, trigger: "cron" }); await runCliAgent({ ...baseRunParams, trigger: "cron", jobId: "cron-job-123" });
expect(prepareCliRunContextMock).not.toHaveBeenCalled(); expect(prepareCliRunContextMock).not.toHaveBeenCalled();
expect(executePreparedCliRunMock).not.toHaveBeenCalled(); expect(executePreparedCliRunMock).not.toHaveBeenCalled();
@@ -144,7 +145,7 @@ describe("runCliAgent cron before_agent_reply seam", () => {
hasHooksMock.mockImplementation((hookName) => hookName === "before_agent_reply"); hasHooksMock.mockImplementation((hookName) => hookName === "before_agent_reply");
runBeforeAgentReplyMock.mockResolvedValue({ handled: true }); runBeforeAgentReplyMock.mockResolvedValue({ handled: true });
const result = await runCliAgent({ ...baseRunParams, trigger: "cron" }); const result = await runCliAgent({ ...baseRunParams, trigger: "cron", jobId: "cron-job-123" });
expect(executePreparedCliRunMock).not.toHaveBeenCalled(); expect(executePreparedCliRunMock).not.toHaveBeenCalled();
expect(result.payloads?.[0]?.text).toBe(SILENT_REPLY_TOKEN); expect(result.payloads?.[0]?.text).toBe(SILENT_REPLY_TOKEN);

View File

@@ -498,6 +498,22 @@ describe("runCliAgent spawn path", () => {
expect(params.extraSystemPromptStatic).toBe("static"); expect(params.extraSystemPromptStatic).toBe("static");
}); });
it("forwards cron jobId through the compat wrapper", () => {
const params = buildRunClaudeCliAgentParams({
sessionId: "openclaw-session",
sessionFile: "/tmp/session.jsonl",
workspaceDir: "/tmp",
prompt: "hi",
timeoutMs: 1_000,
runId: "run-claude-jobid-wrapper",
trigger: "cron",
jobId: "cron-job-123",
});
expect(params.trigger).toBe("cron");
expect(params.jobId).toBe("cron-job-123");
});
it("runs CLI through supervisor and returns payload", async () => { it("runs CLI through supervisor and returns payload", async () => {
supervisorSpawnMock.mockResolvedValueOnce( supervisorSpawnMock.mockResolvedValueOnce(
createManagedRun({ createManagedRun({

View File

@@ -70,6 +70,7 @@ export async function runCliAgent(params: RunCliAgentParams): Promise<EmbeddedPi
if (hookRunner?.hasHooks("before_agent_reply")) { if (hookRunner?.hasHooks("before_agent_reply")) {
const hookContext = { const hookContext = {
runId: params.runId, runId: params.runId,
jobId: params.jobId,
agentId: params.agentId, agentId: params.agentId,
sessionKey: params.sessionKey, sessionKey: params.sessionKey,
sessionId: params.sessionId, sessionId: params.sessionId,
@@ -143,6 +144,7 @@ export async function runPreparedCliAgent(
} as const; } as const;
const hookContext = { const hookContext = {
runId: params.runId, runId: params.runId,
jobId: params.jobId,
agentId: params.agentId, agentId: params.agentId,
sessionKey: params.sessionKey, sessionKey: params.sessionKey,
sessionId: params.sessionId, sessionId: params.sessionId,
@@ -389,6 +391,7 @@ export function buildRunClaudeCliAgentParams(params: RunClaudeCliAgentParams): R
thinkLevel: params.thinkLevel, thinkLevel: params.thinkLevel,
timeoutMs: params.timeoutMs, timeoutMs: params.timeoutMs,
runId: params.runId, runId: params.runId,
jobId: params.jobId,
extraSystemPrompt: params.extraSystemPrompt, extraSystemPrompt: params.extraSystemPrompt,
extraSystemPromptStatic: params.extraSystemPromptStatic, extraSystemPromptStatic: params.extraSystemPromptStatic,
ownerNumbers: params.ownerNumbers, ownerNumbers: params.ownerNumbers,

View File

@@ -25,6 +25,7 @@ export type RunCliAgentParams = {
thinkLevel?: ThinkLevel; thinkLevel?: ThinkLevel;
timeoutMs: number; timeoutMs: number;
runId: string; runId: string;
jobId?: string;
extraSystemPrompt?: string; extraSystemPrompt?: string;
/** Static portion of extraSystemPrompt (excluding per-message inbound metadata) for session reuse hashing. */ /** Static portion of extraSystemPrompt (excluding per-message inbound metadata) for session reuse hashing. */
extraSystemPromptStatic?: string; extraSystemPromptStatic?: string;

View File

@@ -2,6 +2,7 @@ import type { PluginHookAgentContext } from "../../plugins/hook-types.js";
export type AgentHarnessHookContext = { export type AgentHarnessHookContext = {
runId: string; runId: string;
jobId?: string;
agentId?: string; agentId?: string;
sessionKey?: string; sessionKey?: string;
sessionId?: string; sessionId?: string;
@@ -16,6 +17,7 @@ export type AgentHarnessHookContext = {
export function buildAgentHookContext(params: AgentHarnessHookContext): PluginHookAgentContext { export function buildAgentHookContext(params: AgentHarnessHookContext): PluginHookAgentContext {
return { return {
runId: params.runId, runId: params.runId,
...(params.jobId ? { jobId: params.jobId } : {}),
...(params.agentId ? { agentId: params.agentId } : {}), ...(params.agentId ? { agentId: params.agentId } : {}),
...(params.sessionKey ? { sessionKey: params.sessionKey } : {}), ...(params.sessionKey ? { sessionKey: params.sessionKey } : {}),
...(params.sessionId ? { sessionId: params.sessionId } : {}), ...(params.sessionId ? { sessionId: params.sessionId } : {}),

View File

@@ -32,12 +32,14 @@ describe("runEmbeddedPiAgent cron before_agent_reply seam", () => {
const result = await runEmbeddedPiAgent({ const result = await runEmbeddedPiAgent({
...overflowBaseRunParams, ...overflowBaseRunParams,
trigger: "cron", trigger: "cron",
jobId: "cron-job-123",
prompt: "__openclaw_memory_core_short_term_promotion_dream__", prompt: "__openclaw_memory_core_short_term_promotion_dream__",
}); });
expect(mockedGlobalHookRunner.runBeforeAgentReply).toHaveBeenCalledWith( expect(mockedGlobalHookRunner.runBeforeAgentReply).toHaveBeenCalledWith(
{ cleanedBody: "__openclaw_memory_core_short_term_promotion_dream__" }, { cleanedBody: "__openclaw_memory_core_short_term_promotion_dream__" },
expect.objectContaining({ expect.objectContaining({
jobId: "cron-job-123",
agentId: "main", agentId: "main",
sessionId: "test-session", sessionId: "test-session",
sessionKey: "test-key", sessionKey: "test-key",

View File

@@ -326,6 +326,7 @@ export async function runEmbeddedPiAgent(
const hookRunner = getGlobalHookRunner(); const hookRunner = getGlobalHookRunner();
const hookCtx = { const hookCtx = {
runId: params.runId, runId: params.runId,
jobId: params.jobId,
agentId: workspaceResolution.agentId, agentId: workspaceResolution.agentId,
sessionKey: resolvedSessionKey, sessionKey: resolvedSessionKey,
sessionId: params.sessionId, sessionId: params.sessionId,

View File

@@ -31,6 +31,8 @@ export type RunEmbeddedPiAgentParams = {
agentAccountId?: string; agentAccountId?: string;
/** What initiated this agent run: "user", "heartbeat", "cron", "memory", "overflow", or "manual". */ /** What initiated this agent run: "user", "heartbeat", "cron", "memory", "overflow", or "manual". */
trigger?: EmbeddedRunTrigger; trigger?: EmbeddedRunTrigger;
/** Stable cron job identifier populated for cron-triggered runs. */
jobId?: string;
/** Relative workspace path that memory-triggered writes are allowed to append to. */ /** Relative workspace path that memory-triggered writes are allowed to append to. */
memoryFlushWritePath?: string; memoryFlushWritePath?: string;
/** Delivery target for topic/thread routing. */ /** Delivery target for topic/thread routing. */

View File

@@ -130,6 +130,7 @@ export function createCronPromptExecutor(params: {
sessionKey: params.agentSessionKey, sessionKey: params.agentSessionKey,
agentId: params.agentId, agentId: params.agentId,
trigger: "cron", trigger: "cron",
jobId: params.job.id,
sessionFile, sessionFile,
workspaceDir: params.workspaceDir, workspaceDir: params.workspaceDir,
config: params.cfgWithAgentDefaults, config: params.cfgWithAgentDefaults,
@@ -163,6 +164,7 @@ export function createCronPromptExecutor(params: {
sessionKey: params.agentSessionKey, sessionKey: params.agentSessionKey,
agentId: params.agentId, agentId: params.agentId,
trigger: "cron", trigger: "cron",
jobId: params.job.id,
cleanupBundleMcpOnRunEnd: params.job.sessionTarget === "isolated", cleanupBundleMcpOnRunEnd: params.job.sessionTarget === "isolated",
allowGatewaySubagentBinding: true, allowGatewaySubagentBinding: true,
senderIsOwner: false, senderIsOwner: false,

View File

@@ -161,6 +161,7 @@ export const isConversationHookName = (hookName: PluginHookName): boolean =>
export type PluginHookAgentContext = { export type PluginHookAgentContext = {
runId?: string; runId?: string;
jobId?: string;
trace?: DiagnosticTraceContext; trace?: DiagnosticTraceContext;
agentId?: string; agentId?: string;
sessionKey?: string; sessionKey?: string;