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

View File

@@ -498,6 +498,22 @@ describe("runCliAgent spawn path", () => {
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 () => {
supervisorSpawnMock.mockResolvedValueOnce(
createManagedRun({

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,6 +31,8 @@ export type RunEmbeddedPiAgentParams = {
agentAccountId?: string;
/** What initiated this agent run: "user", "heartbeat", "cron", "memory", "overflow", or "manual". */
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. */
memoryFlushWritePath?: string;
/** Delivery target for topic/thread routing. */

View File

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

View File

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