11import { randomUUID } from "node:crypto" ;
2- import type { AppliedCommentResult , CommentToolInput , HunkSessionRegistration , HunkSessionSnapshot , ListedSession } from "./types" ;
2+ import type {
3+ AppliedCommentResult ,
4+ CommentToolInput ,
5+ HunkSessionRegistration ,
6+ HunkSessionSnapshot ,
7+ ListedSession ,
8+ NavigateToHunkToolInput ,
9+ NavigatedSelectionResult ,
10+ SelectedSessionContext ,
11+ SessionCommandResult ,
12+ SessionServerMessage ,
13+ SessionTargetInput ,
14+ } from "./types" ;
315
416interface PendingCommand {
517 sessionId : string ;
6- resolve : ( result : AppliedCommentResult ) => void ;
18+ resolve : ( result : SessionCommandResult ) => void ;
719 reject : ( error : Error ) => void ;
820 timeout : Timer ;
921}
@@ -29,6 +41,14 @@ function describeSessionChoices(sessions: ListedSession[]) {
2941 return sessions . map ( ( session ) => `${ session . sessionId } (${ session . title } )` ) . join ( ", " ) ;
3042}
3143
44+ function findSelectedFile ( session : ListedSession ) {
45+ return session . files . find (
46+ ( file ) => file . id === session . snapshot . selectedFileId
47+ || file . path === session . snapshot . selectedFilePath
48+ || file . previousPath === session . snapshot . selectedFilePath ,
49+ ) ?? null ;
50+ }
51+
3252/** Resolve which live Hunk session one external command should target. */
3353export function resolveSessionTarget ( sessions : ListedSession [ ] , selector : SessionTargetSelector ) {
3454 if ( selector . sessionId ) {
@@ -98,6 +118,29 @@ export class HunkDaemonState {
98118 return resolveSessionTarget ( this . listSessions ( ) , selector ) ;
99119 }
100120
121+ getSelectedContext ( selector : SessionTargetSelector ) : SelectedSessionContext {
122+ const session = this . getSession ( selector ) ;
123+ const selectedFile = findSelectedFile ( session ) ;
124+
125+ return {
126+ sessionId : session . sessionId ,
127+ title : session . title ,
128+ sourceLabel : session . sourceLabel ,
129+ repoRoot : session . repoRoot ,
130+ inputKind : session . inputKind ,
131+ selectedFile,
132+ selectedHunk : selectedFile
133+ ? {
134+ index : session . snapshot . selectedHunkIndex ,
135+ oldRange : session . snapshot . selectedHunkOldRange ,
136+ newRange : session . snapshot . selectedHunkNewRange ,
137+ }
138+ : null ,
139+ showAgentNotes : session . snapshot . showAgentNotes ,
140+ liveCommentCount : session . snapshot . liveCommentCount ,
141+ } ;
142+ }
143+
101144 getPendingCommandCount ( ) {
102145 return this . pendingCommands . size ;
103146 }
@@ -182,52 +225,25 @@ export class HunkDaemonState {
182225 return removed ;
183226 }
184227
185- async sendComment ( input : CommentToolInput ) {
186- const session = resolveSessionTarget ( this . listSessions ( ) , {
187- sessionId : input . sessionId ,
188- repoRoot : input . repoRoot ,
189- } ) ;
190- const requestId = randomUUID ( ) ;
191-
192- return new Promise < AppliedCommentResult > ( ( resolve , reject ) => {
193- const timeout = setTimeout ( ( ) => {
194- this . pendingCommands . delete ( requestId ) ;
195- reject ( new Error ( "Timed out waiting for the Hunk session to apply the comment." ) ) ;
196- } , 15_000 ) ;
197-
198- this . pendingCommands . set ( requestId , {
199- sessionId : session . sessionId ,
200- resolve,
201- reject,
202- timeout,
203- } ) ;
204-
205- const entry = this . sessions . get ( session . sessionId ) ;
206- if ( ! entry ) {
207- clearTimeout ( timeout ) ;
208- this . pendingCommands . delete ( requestId ) ;
209- reject ( new Error ( "The targeted Hunk session is no longer connected." ) ) ;
210- return ;
211- }
228+ sendComment ( input : CommentToolInput ) {
229+ return this . sendCommand < AppliedCommentResult , "comment" > (
230+ { sessionId : input . sessionId , repoRoot : input . repoRoot } ,
231+ "comment" ,
232+ input ,
233+ "Timed out waiting for the Hunk session to apply the comment." ,
234+ ) ;
235+ }
212236
213- try {
214- entry . socket . send (
215- JSON . stringify ( {
216- type : "command" ,
217- requestId,
218- command : "comment" ,
219- input,
220- } ) ,
221- ) ;
222- } catch ( error ) {
223- clearTimeout ( timeout ) ;
224- this . pendingCommands . delete ( requestId ) ;
225- reject ( error instanceof Error ? error : new Error ( "The targeted Hunk session could not receive the command." ) ) ;
226- }
227- } ) ;
237+ sendNavigateToHunk ( input : NavigateToHunkToolInput ) {
238+ return this . sendCommand < NavigatedSelectionResult , "navigate_to_hunk" > (
239+ { sessionId : input . sessionId , repoRoot : input . repoRoot } ,
240+ "navigate_to_hunk" ,
241+ input ,
242+ "Timed out waiting for the Hunk session to navigate to the requested hunk." ,
243+ ) ;
228244 }
229245
230- handleCommandResult ( message : { requestId : string ; ok : boolean ; result ?: AppliedCommentResult ; error ?: string } ) {
246+ handleCommandResult ( message : { requestId : string ; ok : boolean ; result ?: SessionCommandResult ; error ?: string } ) {
231247 const pending = this . pendingCommands . get ( message . requestId ) ;
232248 if ( ! pending ) {
233249 return ;
@@ -255,6 +271,53 @@ export class HunkDaemonState {
255271 this . sessions . clear ( ) ;
256272 }
257273
274+ private sendCommand < ResultType extends SessionCommandResult , CommandName extends SessionServerMessage [ "command" ] > (
275+ selector : SessionTargetInput ,
276+ command : CommandName ,
277+ input : Extract < SessionServerMessage , { command : CommandName } > [ "input" ] ,
278+ timeoutMessage : string ,
279+ ) {
280+ const session = resolveSessionTarget ( this . listSessions ( ) , selector ) ;
281+ const requestId = randomUUID ( ) ;
282+
283+ return new Promise < ResultType > ( ( resolve , reject ) => {
284+ const timeout = setTimeout ( ( ) => {
285+ this . pendingCommands . delete ( requestId ) ;
286+ reject ( new Error ( timeoutMessage ) ) ;
287+ } , 15_000 ) ;
288+
289+ this . pendingCommands . set ( requestId , {
290+ sessionId : session . sessionId ,
291+ resolve : ( result ) => resolve ( result as ResultType ) ,
292+ reject,
293+ timeout,
294+ } ) ;
295+
296+ const entry = this . sessions . get ( session . sessionId ) ;
297+ if ( ! entry ) {
298+ clearTimeout ( timeout ) ;
299+ this . pendingCommands . delete ( requestId ) ;
300+ reject ( new Error ( "The targeted Hunk session is no longer connected." ) ) ;
301+ return ;
302+ }
303+
304+ try {
305+ const message = {
306+ type : "command" ,
307+ requestId,
308+ command,
309+ input,
310+ } as Extract < SessionServerMessage , { command : CommandName } > ;
311+
312+ entry . socket . send ( JSON . stringify ( message ) ) ;
313+ } catch ( error ) {
314+ clearTimeout ( timeout ) ;
315+ this . pendingCommands . delete ( requestId ) ;
316+ reject ( error instanceof Error ? error : new Error ( "The targeted Hunk session could not receive the command." ) ) ;
317+ }
318+ } ) ;
319+ }
320+
258321 private removeSession ( sessionId : string , reason : string ) {
259322 const entry = this . sessions . get ( sessionId ) ;
260323 if ( ! entry ) {
0 commit comments