File size: 2,423 Bytes
fdc8b59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { useEffect, useRef, useSyncExternalStore } from 'react'
import { useAppState, useSetAppState } from '../state/AppState.js'
import {
  buildContinuationPrompt,
  getGoalContinuationGoalId,
  isGoalContinuationPrompt,
} from '../utils/goal.js'
import {
  enqueue,
  getCommandQueue,
  removeByFilter,
} from '../utils/messageQueueManager.js'
import type { QueryGuard } from '../utils/QueryGuard.js'

/**
 * Auto-continuation hook for /goal. Subscribes to the QueryGuard's
 * isActive snapshot via useSyncExternalStore. When isActive transitions
 * from true -> false (turn just ended), and a goal is pursuing, enqueue
 * a meta continuation prompt.
 *
 * Bails out for: no goal, non-pursuing status, or plan mode.
 */
export function useGoalAutoContinue(queryGuard: QueryGuard): void {
  const isActive = useSyncExternalStore(
    queryGuard.subscribe,
    queryGuard.getSnapshot,
  )
  const goal = useAppState(s => s.goal)
  const mode = useAppState(s => s.toolPermissionContext.mode)
  const setAppState = useSetAppState()

  const wasActiveRef = useRef(isActive)

  useEffect(() => {
    const wasActive = wasActiveRef.current
    wasActiveRef.current = isActive

    // Only fire on running -> idle transition.
    if (!(wasActive && !isActive)) return

    if (!goal) return
    if (goal.status !== 'pursuing') return
    if (mode === 'plan') return

    const now = Date.now()

    // User/control input wins over autonomous continuation.
    const queue = getCommandQueue()
    const hasBlockingQueuedWork = queue.some(
      cmd => !isGoalContinuationPrompt(cmd.value),
    )
    if (hasBlockingQueuedWork) return

    const alreadyQueuedForThisGoal = queue.some(
      cmd => getGoalContinuationGoalId(cmd.value) === goal.id,
    )
    if (alreadyQueuedForThisGoal) return

    removeByFilter(cmd => {
      if (!isGoalContinuationPrompt(cmd.value)) return false
      return getGoalContinuationGoalId(cmd.value) !== goal.id
    })

    const prompt = buildContinuationPrompt(goal, now)
    enqueue({
      mode: 'prompt',
      value: prompt,
      priority: 'later',
      isMeta: true,
    })

    setAppState(prev => ({
      ...prev,
      goal: prev.goal && prev.goal.id === goal.id
        ? {
            ...prev.goal,
            continuationCount: prev.goal.continuationCount + 1,
            lastUpdatedAt: now,
          }
        : prev.goal,
    }))
  }, [isActive, goal, mode, setAppState])
}