DreamTask.ts
tasks/DreamTask/DreamTask.ts
158
Lines
4988
Bytes
9
Exports
4
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with tasks-background-jobs. It contains 158 lines, 4 detected imports, and 9 detected exports.
Important relationships
- No related files detected.
Detected exports
DreamTurnDreamPhaseDreamTaskStateisDreamTaskregisterDreamTaskaddDreamTurncompleteDreamTaskfailDreamTaskDreamTask
Keywords
tasksetappstatedreamabortcontrollerpriormtimetaskiddreamtaskstateupdatetaskstateturnfilestouched
Detected imports
../../services/autoDream/consolidationLock.js../../Task.js../../Task.js../../utils/task/framework.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
// Background task entry for auto-dream (memory consolidation subagent).
// Makes the otherwise-invisible forked agent visible in the footer pill and
// Shift+Down dialog. The dream agent itself is unchanged — this is pure UI
// surfacing via the existing task registry.
import { rollbackConsolidationLock } from '../../services/autoDream/consolidationLock.js'
import type { SetAppState, Task, TaskStateBase } from '../../Task.js'
import { createTaskStateBase, generateTaskId } from '../../Task.js'
import { registerTask, updateTaskState } from '../../utils/task/framework.js'
// Keep only the N most recent turns for live display.
const MAX_TURNS = 30
// A single assistant turn from the dream agent, tool uses collapsed to a count.
export type DreamTurn = {
text: string
toolUseCount: number
}
// No phase detection — the dream prompt has a 4-stage structure
// (orient/gather/consolidate/prune) but we don't parse it. Just flip from
// 'starting' to 'updating' when the first Edit/Write tool_use lands.
export type DreamPhase = 'starting' | 'updating'
export type DreamTaskState = TaskStateBase & {
type: 'dream'
phase: DreamPhase
sessionsReviewing: number
/**
* Paths observed in Edit/Write tool_use blocks via onMessage. This is an
* INCOMPLETE reflection of what the dream agent actually changed — it misses
* any bash-mediated writes and only captures the tool calls we pattern-match.
* Treat as "at least these were touched", not "only these were touched".
*/
filesTouched: string[]
/** Assistant text responses, tool uses collapsed. Prompt is NOT included. */
turns: DreamTurn[]
abortController?: AbortController
/** Stashed so kill can rewind the lock mtime (same path as fork-failure). */
priorMtime: number
}
export function isDreamTask(task: unknown): task is DreamTaskState {
return (
typeof task === 'object' &&
task !== null &&
'type' in task &&
task.type === 'dream'
)
}
export function registerDreamTask(
setAppState: SetAppState,
opts: {
sessionsReviewing: number
priorMtime: number
abortController: AbortController
},
): string {
const id = generateTaskId('dream')
const task: DreamTaskState = {
...createTaskStateBase(id, 'dream', 'dreaming'),
type: 'dream',
status: 'running',
phase: 'starting',
sessionsReviewing: opts.sessionsReviewing,
filesTouched: [],
turns: [],
abortController: opts.abortController,
priorMtime: opts.priorMtime,
}
registerTask(task, setAppState)
return id
}
export function addDreamTurn(
taskId: string,
turn: DreamTurn,
touchedPaths: string[],
setAppState: SetAppState,
): void {
updateTaskState<DreamTaskState>(taskId, setAppState, task => {
const seen = new Set(task.filesTouched)
const newTouched = touchedPaths.filter(p => !seen.has(p) && seen.add(p))
// Skip the update entirely if the turn is empty AND nothing new was
// touched. Avoids re-rendering on pure no-ops.
if (
turn.text === '' &&
turn.toolUseCount === 0 &&
newTouched.length === 0
) {
return task
}
return {
...task,
phase: newTouched.length > 0 ? 'updating' : task.phase,
filesTouched:
newTouched.length > 0
? [...task.filesTouched, ...newTouched]
: task.filesTouched,
turns: task.turns.slice(-(MAX_TURNS - 1)).concat(turn),
}
})
}
export function completeDreamTask(
taskId: string,
setAppState: SetAppState,
): void {
// notified: true immediately — dream has no model-facing notification path
// (it's UI-only), and eviction requires terminal + notified. The inline
// appendSystemMessage completion note IS the user surface.
updateTaskState<DreamTaskState>(taskId, setAppState, task => ({
...task,
status: 'completed',
endTime: Date.now(),
notified: true,
abortController: undefined,
}))
}
export function failDreamTask(taskId: string, setAppState: SetAppState): void {
updateTaskState<DreamTaskState>(taskId, setAppState, task => ({
...task,
status: 'failed',
endTime: Date.now(),
notified: true,
abortController: undefined,
}))
}
export const DreamTask: Task = {
name: 'DreamTask',
type: 'dream',
async kill(taskId, setAppState) {
let priorMtime: number | undefined
updateTaskState<DreamTaskState>(taskId, setAppState, task => {
if (task.status !== 'running') return task
task.abortController?.abort()
priorMtime = task.priorMtime
return {
...task,
status: 'killed',
endTime: Date.now(),
notified: true,
abortController: undefined,
}
})
// Rewind the lock mtime so the next session can retry. Same path as the
// fork-failure catch in autoDream.ts. If updateTaskState was a no-op
// (already terminal), priorMtime stays undefined and we skip.
if (priorMtime !== undefined) {
await rollbackConsolidationLock(priorMtime)
}
},
}