1+ import { POSTHOG_NOTIFICATIONS } from "./acp-extensions.js" ;
12import {
23 createAcpConnection ,
34 type InProcessAcpConnection ,
45} from "./adapters/acp-connection.js" ;
56import { PostHogAPIClient } from "./posthog-api.js" ;
67import { SessionLogWriter } from "./session-log-writer.js" ;
7- import type { AgentConfig , TaskExecutionOptions } from "./types.js" ;
8+ import { TreeTracker } from "./tree-tracker.js" ;
9+ import type {
10+ AgentConfig ,
11+ AgentMode ,
12+ DeviceInfo ,
13+ TaskExecutionOptions ,
14+ TreeSnapshotEvent ,
15+ } from "./types.js" ;
816import { Logger } from "./utils/logger.js" ;
917
1018export class Agent {
1119 private posthogAPI ?: PostHogAPIClient ;
1220 private logger : Logger ;
1321 private acpConnection ?: InProcessAcpConnection ;
14- private taskRunId ?: string ;
1522 private sessionLogWriter ?: SessionLogWriter ;
23+ private currentRunId ?: string ;
24+ private currentTaskId ?: string ;
25+ private treeTracker ?: TreeTracker ;
26+ private deviceInfo ?: DeviceInfo ;
27+ private agentMode : AgentMode = "interactive" ;
28+ private isExecutingTool : boolean = false ;
1629 public debug : boolean ;
1730
1831 constructor ( config : AgentConfig ) {
@@ -55,7 +68,29 @@ export class Agent {
5568 ) : Promise < InProcessAcpConnection > {
5669 await this . _configureLlmGateway ( ) ;
5770
58- this . taskRunId = taskRunId ;
71+ const cwd = options . repositoryPath ;
72+
73+ // Use taskRunId as sessionId - they are the same identifier
74+ this . currentRunId = taskRunId ;
75+ this . currentTaskId = taskId ;
76+
77+ // Initialize TreeTracker for state capture if we have a repository path
78+ if ( cwd ) {
79+ this . treeTracker = new TreeTracker ( {
80+ repositoryPath : cwd ,
81+ taskId,
82+ runId : taskRunId ,
83+ apiClient : this . posthogAPI ,
84+ logger : this . logger . child ( "TreeTracker" ) ,
85+ } ) ;
86+ }
87+
88+ // Set device info for local mode
89+ this . deviceInfo = {
90+ id : `local-${ process . pid } ` ,
91+ type : "local" ,
92+ name : process . env . HOSTNAME || process . env . USER || "local" ,
93+ } ;
5994
6095 this . acpConnection = createAcpConnection ( {
6196 adapter : options . adapter ,
@@ -74,30 +109,133 @@ export class Agent {
74109 ) : Promise < void > {
75110 this . logger . info ( "Attaching PR to task run" , { taskId, prUrl, branchName } ) ;
76111
77- if ( ! this . posthogAPI || ! this . taskRunId ) {
112+ if ( ! this . posthogAPI || ! this . currentRunId ) {
78113 const error = new Error (
79114 "PostHog API not configured or no active run. Cannot attach PR to task." ,
80115 ) ;
81116 this . logger . error ( "PostHog API not configured" , error ) ;
82117 throw error ;
83118 }
84119
85- const updates : any = {
120+ const updates : Record < string , unknown > = {
86121 output : { pr_url : prUrl } ,
87122 } ;
88123 if ( branchName ) {
89124 updates . branch = branchName ;
90125 }
91126
92- await this . posthogAPI . updateTaskRun ( taskId , this . taskRunId , updates ) ;
127+ await this . posthogAPI . updateTaskRun ( taskId , this . currentRunId , updates ) ;
93128 this . logger . debug ( "PR attached to task run" , {
94129 taskId,
95- taskRunId : this . taskRunId ,
130+ taskRunId : this . currentRunId ,
96131 prUrl,
97132 } ) ;
98133 }
99134
100135 async cleanup ( ) : Promise < void > {
101136 await this . acpConnection ?. cleanup ( ) ;
102137 }
138+
139+ /**
140+ * Stop the agent gracefully, capturing final state.
141+ * Emits a tree_snapshot event with interrupted flag if mid-tool.
142+ */
143+ async stop ( ) : Promise < TreeSnapshotEvent | null > {
144+ const interrupted = this . isExecutingTool ;
145+
146+ this . logger . info ( "Stopping agent" , {
147+ interrupted,
148+ taskId : this . currentTaskId ,
149+ runId : this . currentRunId ,
150+ } ) ;
151+
152+ // Capture tree snapshot immediately
153+ let snapshot : TreeSnapshotEvent | null = null ;
154+ if ( this . treeTracker ) {
155+ const treeSnapshot = await this . treeTracker . captureTree ( { interrupted } ) ;
156+ if ( treeSnapshot ) {
157+ snapshot = {
158+ ...treeSnapshot ,
159+ device : this . deviceInfo ,
160+ } ;
161+
162+ // Emit tree_snapshot event
163+ await this . emitTreeSnapshot ( snapshot ) ;
164+ }
165+ }
166+
167+ // Flush session log writer
168+ if ( this . sessionLogWriter && this . currentRunId ) {
169+ await this . sessionLogWriter . flush ( this . currentRunId ) ;
170+ }
171+
172+ this . logger . info ( "Agent stopped" , {
173+ hasSnapshot : ! ! snapshot ,
174+ interrupted,
175+ } ) ;
176+
177+ return snapshot ;
178+ }
179+
180+ /**
181+ * Emit a tree_snapshot event to the log.
182+ */
183+ private async emitTreeSnapshot ( snapshot : TreeSnapshotEvent ) : Promise < void > {
184+ if ( ! this . acpConnection ) return ;
185+
186+ await this . acpConnection . agentConnection . extNotification ?.(
187+ POSTHOG_NOTIFICATIONS . TREE_SNAPSHOT ,
188+ snapshot as unknown as Record < string , unknown > ,
189+ ) ;
190+ }
191+
192+ /**
193+ * Set device info for tracking where work happens.
194+ */
195+ setDeviceInfo ( info : DeviceInfo ) : void {
196+ this . deviceInfo = info ;
197+ }
198+
199+ /**
200+ * Get current device info.
201+ */
202+ getDeviceInfo ( ) : DeviceInfo | undefined {
203+ return this . deviceInfo ;
204+ }
205+
206+ /**
207+ * Set agent mode (interactive or background).
208+ */
209+ setAgentMode ( mode : AgentMode ) : void {
210+ const previousMode = this . agentMode ;
211+ this . agentMode = mode ;
212+
213+ if ( previousMode !== mode && this . acpConnection ) {
214+ this . acpConnection . agentConnection . extNotification ?.(
215+ POSTHOG_NOTIFICATIONS . MODE_CHANGE ,
216+ { mode, previous_mode : previousMode } ,
217+ ) ;
218+ }
219+ }
220+
221+ /**
222+ * Get current agent mode.
223+ */
224+ getAgentMode ( ) : AgentMode {
225+ return this . agentMode ;
226+ }
227+
228+ /**
229+ * Mark tool execution started/ended (used by stop() to determine interrupted state).
230+ */
231+ setToolExecuting ( executing : boolean ) : void {
232+ this . isExecutingTool = executing ;
233+ }
234+
235+ /**
236+ * Get the tree tracker instance.
237+ */
238+ getTreeTracker ( ) : TreeTracker | undefined {
239+ return this . treeTracker ;
240+ }
103241}
0 commit comments