feat: extend agentmon hook with agent:bootstrap for embedded/cron runs

- Add agent:bootstrap handler to capture run.start events for cron and
  automation runs that bypass the message:received path
- Remove dead event subscriptions (tool_result_persist, session:compact:*)
  which are plugin hook events and never fire through triggerInternalHook
- Remove AGENTMON_INGEST_URL from requires.env since handler has a
  hardcoded fallback URL
- Drop activeCompactions map (no longer needed after removing compaction handlers)

Deployed to zap VM with hooks.internal.enabled=true in openclaw.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
William Valentin
2026-03-15 17:32:32 -07:00
parent 13356adfbd
commit e7be607db4
2 changed files with 23 additions and 82 deletions
+1 -6
View File
@@ -9,13 +9,8 @@ metadata:
- "command:reset" - "command:reset"
- "message:received" - "message:received"
- "message:sent" - "message:sent"
- "tool_result_persist" - "agent:bootstrap"
- "session:compact:before"
- "session:compact:after"
export: "default" export: "default"
requires:
env:
- "AGENTMON_INGEST_URL"
--- ---
# Agentmon Telemetry Hook # Agentmon Telemetry Hook
+22 -76
View File
@@ -14,7 +14,6 @@ let flushTimer: ReturnType<typeof setTimeout> | null = null;
let isFlushing = false; let isFlushing = false;
const activeRuns = new Map<string, string>(); const activeRuns = new Map<string, string>();
const activeCompactions = new Map<string, string>();
function isRecord(value: unknown): value is Dict { function isRecord(value: unknown): value is Dict {
return value !== null && typeof value === 'object' && !Array.isArray(value); return value !== null && typeof value === 'object' && !Array.isArray(value);
@@ -274,7 +273,6 @@ const handler = async (rawEvent: unknown) => {
enqueue(buildEnvelope('session.end', sessionKey)); enqueue(buildEnvelope('session.end', sessionKey));
if (sessionKey) { if (sessionKey) {
activeRuns.delete(sessionKey); activeRuns.delete(sessionKey);
activeCompactions.delete(sessionKey);
} }
return; return;
} }
@@ -284,7 +282,28 @@ const handler = async (rawEvent: unknown) => {
enqueue(buildEnvelope('session.start', sessionKey)); enqueue(buildEnvelope('session.start', sessionKey));
if (sessionKey) { if (sessionKey) {
activeRuns.delete(sessionKey); activeRuns.delete(sessionKey);
activeCompactions.delete(sessionKey); }
return;
}
if (eventName === 'agent:bootstrap') {
// Only emit run.start if no run is already active for this session.
// Interactive message-channel sessions already emit run.start via
// message:received; this handler captures cron/automation embedded runs.
const existingRunId = sessionKey ? activeRuns.get(sessionKey) : undefined;
if (!existingRunId) {
const runId = randomUUID();
if (sessionKey) {
activeRuns.set(sessionKey, runId);
}
enqueue(buildEnvelope('run.start', sessionKey, {
runId,
attributes: {
agent_id: pickString(context.agentId as string | undefined),
run_kind: 'embedded',
},
}));
} }
return; return;
} }
@@ -330,79 +349,6 @@ const handler = async (rawEvent: unknown) => {
return; return;
} }
if (eventName === 'tool_result_persist') {
const runId = sessionKey ? activeRuns.get(sessionKey) : undefined;
const spanId = randomUUID();
const success = context.success !== false && !context.error;
const toolName = pickString(context.toolName, context.tool_name, context.name) || 'unknown_tool';
const payload: Dict = {
status: success ? 'success' : 'error',
};
const duration = pickNumber(context.duration_ms, context.durationMs, context.elapsed_ms);
if (duration !== undefined) {
payload.duration_ms = duration;
}
const resultPreview = truncate(context.result ?? context.output, 500);
if (resultPreview) {
payload.result_preview = resultPreview;
}
enqueue(buildEnvelope('span.end', sessionKey, {
runId,
spanId,
attributes: {
span_kind: 'tool',
name: toolName,
},
payload,
}));
if (!success) {
emitError(sessionKey, runId, spanId, context.error);
}
return;
}
if (eventName === 'session:compact:before') {
const runId = sessionKey ? activeRuns.get(sessionKey) : undefined;
const spanId = randomUUID();
if (sessionKey) {
activeCompactions.set(sessionKey, spanId);
}
enqueue(buildEnvelope('span.start', sessionKey, {
runId,
spanId,
attributes: {
span_kind: 'internal',
name: 'context_compaction',
},
}));
return;
}
if (eventName === 'session:compact:after') {
const runId = sessionKey ? activeRuns.get(sessionKey) : undefined;
const spanId = (sessionKey && activeCompactions.get(sessionKey)) || randomUUID();
if (sessionKey) {
activeCompactions.delete(sessionKey);
}
enqueue(buildEnvelope('span.end', sessionKey, {
runId,
spanId,
attributes: {
span_kind: 'internal',
name: 'context_compaction',
},
payload: {
status: 'success',
duration_ms: pickNumber(context.duration_ms, context.durationMs, context.elapsed_ms),
},
}));
}
} catch { } catch {
console.debug('[agentmon] handler error'); console.debug('[agentmon] handler error');
} }