fix(monitor): improve subagent spawn inference and minimap sizing (#22)

* refactor(clawdbot): update spawn inference logic

- Replaced parent session activity tracking with a pending spawns array to record Task tool calls.
- Adjusted the spawn inference window to 10 seconds, ensuring accurate tracking of recent Task calls.
- Enhanced the inferSpawnedBy function to utilize the new pending spawns structure for improved session mapping.
- Removed obsolete parent session activity tracking code for cleaner implementation.

* fix(clawdbot): normalize tool name handling in action tracking

- Updated the action tracking logic to convert tool names to lowercase for consistent comparison, ensuring accurate detection of 'Task' tool calls in both addAction and hydrateFromServer functions.
- Enhanced event parsing to process tool events more effectively, improving overall event handling and session state management.

* refactor(clawdbot): enhance spawn inference with parent session activity tracking

- Introduced a new Map to track recent parent session activity, providing a fallback for spawn inference when Task tool calls are not available.
- Updated the recordPendingSpawn and inferSpawnedBy functions to utilize this new tracking mechanism, improving the accuracy of session mapping.
- Enhanced the addAction and hydrateFromServer functions to incorporate parent session activity tracking, ensuring comprehensive spawn history management.
- Cleared parent session activity during collection resets to maintain data integrity.

* refactor(clawdbot): improve parent session action tracking for spawn inference

- Replaced the previous parent session activity tracking with a new structure to maintain a history of recent actions, allowing for more precise spawn inference.
- Updated the trackParentAction function to log actions with timestamps and manage a maximum history length.
- Modified the inferSpawnedBy function to utilize the new action history for better accuracy in linking subagents to their parent sessions.
- Adjusted the addAction and hydrateFromServer functions to reflect the new tracking mechanism, ensuring comprehensive session mapping.
- Cleared the action history during collection resets to maintain data integrity.

* style(monitor): adjust ActionGraph component dimensions

- Updated the ActionGraphInner component to include width and height styles, enhancing the layout and visual consistency of the graph representation.
This commit is contained in:
Luciano Castillo
2026-01-29 21:22:47 -05:00
committed by GitHub
parent 3c370e3595
commit 8db30f1f12
3 changed files with 70 additions and 40 deletions
+1 -1
View File
@@ -791,7 +791,7 @@ function ActionGraphInner({
}}
maskColor="rgba(10, 10, 15, 0.8)"
className="bg-shell-900! border-shell-700!"
style={{ backgroundColor: '#0a0a0f' }}
style={{ backgroundColor: '#0a0a0f', width: 100, height: 75 }}
/>
</ReactFlow>
</div>
+57 -39
View File
@@ -12,12 +12,13 @@ import {
// Track runId → sessionKey mapping (learned from chat events)
const runSessionMap = new Map<string, string>()
// Track recent activity on parent (non-subagent) sessions for spawn inference
// Maps sessionKey → lastActivityTimestamp
const parentSessionActivity = new Map<string, number>()
// Track recent parent session actions with precise timestamps
// Stores the last few action timestamps per parent session
const parentActionHistory = new Map<string, number[]>()
const MAX_ACTION_HISTORY = 10
// Time window for spawn inference - parent must have been active within this window
const SPAWN_INFERENCE_WINDOW_MS = 5000
// Time window for spawn inference
const SPAWN_INFERENCE_WINDOW_MS = 10000
function isSubagentSession(key: string): boolean {
return key.includes('subagent')
@@ -27,31 +28,58 @@ function isParentSession(key: string): boolean {
return !isSubagentSession(key) && !key.includes('lifecycle')
}
// Infer which parent session spawned this subagent based on recent activity
// Track an action on a parent session with its timestamp
function trackParentAction(sessionKey: string, timestamp?: number) {
if (!isParentSession(sessionKey)) return
const ts = timestamp ?? Date.now()
let history = parentActionHistory.get(sessionKey)
if (!history) {
history = []
parentActionHistory.set(sessionKey, history)
}
history.push(ts)
// Keep only recent entries
if (history.length > MAX_ACTION_HISTORY) {
history.shift()
}
}
// Infer which parent session spawned this subagent
// Finds the parent with the most recent action before the subagent's timestamp
function inferSpawnedBy(subagentKey: string, timestamp?: number): string | undefined {
if (!isSubagentSession(subagentKey)) return undefined
const now = timestamp ?? Date.now()
const subagentTime = timestamp ?? Date.now()
const cutoff = subagentTime - SPAWN_INFERENCE_WINDOW_MS
let bestParent: string | undefined
let bestTime = 0
for (const [parentKey, activityTime] of parentSessionActivity) {
// Must be within inference window
if (now - activityTime > SPAWN_INFERENCE_WINDOW_MS) continue
// Pick most recently active parent
if (activityTime > bestTime) {
bestTime = activityTime
bestParent = parentKey
for (const [parentKey, history] of parentActionHistory) {
// Find the most recent action from this parent that's before the subagent time
for (let i = history.length - 1; i >= 0; i--) {
const actionTime = history[i]!
// Must be before subagent appeared and within window
if (actionTime <= subagentTime && actionTime >= cutoff) {
if (actionTime > bestTime) {
bestTime = actionTime
bestParent = parentKey
}
break // Found the most recent valid action for this parent
}
}
}
return bestParent
}
if (bestParent) {
console.log(`[spawn] linked ${subagentKey} to ${bestParent} (action ${subagentTime - bestTime}ms before)`)
} else {
console.log(`[spawn] could not infer parent for ${subagentKey}`)
}
// Track activity on a parent session
function trackParentActivity(sessionKey: string, timestamp?: number) {
if (!isParentSession(sessionKey)) return
parentSessionActivity.set(sessionKey, timestamp ?? Date.now())
return bestParent
}
export const sessionsCollection = createCollection(
@@ -164,11 +192,6 @@ function createPlaceholderExec(event: MonitorExecEvent, sessionKey?: string): Mo
// Helper to update or insert session
export function upsertSession(session: MonitorSession) {
// Track activity on parent sessions
if (isParentSession(session.key)) {
trackParentActivity(session.key, session.lastActivityAt)
}
const existing = sessionsCollection.state.get(session.key)
if (existing) {
@@ -207,9 +230,9 @@ export function addAction(action: MonitorAction) {
backfillExecSessionKey(action.runId, action.sessionKey)
}
// Track activity on parent sessions for spawn inference
// Track parent session actions for spawn inference
if (isParentSession(action.sessionKey)) {
trackParentActivity(action.sessionKey, action.timestamp)
trackParentAction(action.sessionKey, action.timestamp)
}
}
@@ -370,12 +393,6 @@ export function updateSessionStatus(
status: MonitorSession['status']
) {
const now = Date.now()
// Track activity on parent sessions
if (isParentSession(key)) {
trackParentActivity(key, now)
}
const session = sessionsCollection.state.get(key)
if (session) {
sessionsCollection.update(key, (draft) => {
@@ -412,7 +429,7 @@ export function updateSession(key: string, update: Partial<MonitorSession>) {
// Clear all data
export function clearCollections() {
runSessionMap.clear()
parentSessionActivity.clear()
parentActionHistory.clear()
for (const session of sessionsCollection.state.values()) {
sessionsCollection.delete(session.key)
}
@@ -478,23 +495,24 @@ export function hydrateFromServer(
// First clear existing data
clearCollections()
// Replay actions first to build parent activity history
// Sort actions by timestamp for replay
const sortedActions = [...actions].sort((a, b) => a.timestamp - b.timestamp)
// First pass: build parent action history for spawn inference
for (const action of sortedActions) {
// Track parent activity without inserting actions yet
if (action.sessionKey && isParentSession(action.sessionKey)) {
trackParentActivity(action.sessionKey, action.timestamp)
trackParentAction(action.sessionKey, action.timestamp)
}
}
// Also track parent sessions by their lastActivityAt
for (const session of sessions) {
if (isParentSession(session.key)) {
trackParentActivity(session.key, session.lastActivityAt)
trackParentAction(session.key, session.lastActivityAt)
}
}
// Now insert all sessions - subagents will get inferred spawnedBy
// Insert all sessions - subagents will get inferred spawnedBy from Task tool calls
for (const session of sessions) {
if (isSubagentSession(session.key)) {
const spawnedBy = session.spawnedBy || inferSpawnedBy(session.key, session.lastActivityAt)
+12
View File
@@ -203,6 +203,18 @@ export function parseEventFrame(
}
}
// Process tool events (tool_use, tool_result)
if (agentEvent.data?.type === 'tool_use' || agentEvent.data?.type === 'tool_result') {
return {
action: agentEventToAction(agentEvent),
session: agentEvent.sessionKey ? {
key: agentEvent.sessionKey,
status: 'thinking',
lastActivityAt: Date.now(),
} : undefined,
}
}
return null
}