@@ -27,6 +27,7 @@ import {
2727} from "@agentclientprotocol/sdk" ;
2828import { runAgentLoop , type AgentLoopResult } from "../core/agent-loop.js" ;
2929import { runAgentLoopContinue } from "../core/agent-loop-continue.js" ;
30+ import { compactDurableSession } from "../core/compact-session.js" ;
3031import { createSessionId } from "../core/session-store.js" ;
3132import { setSessionExternalDirectories , validateExternalDirectories } from "../core/external-directories.js" ;
3233import { loadSession as loadSessionRecord } from "../core/session-loader.js" ;
@@ -36,7 +37,7 @@ import { DEFAULT_AGENT_MODE } from "../core/mode-router.js";
3637import type { AgentEvent } from "../core/events.js" ;
3738import type { ApprovalMode } from "../core/approval.js" ;
3839import type { AgentMode } from "../core/types.js" ;
39- import type { ModelQuestion } from "../core/model-client.js" ;
40+ import { HeuristicModelClient , type ModelQuestion } from "../core/model-client.js" ;
4041import type { ApprovalControlDecision } from "../core/stdin-control.js" ;
4142import { ProviderModelClient } from "../providers/provider-model-client.js" ;
4243import { listBuiltinProviderModels , resolveModel } from "../providers/model-registry.js" ;
@@ -81,6 +82,22 @@ type AcpSession = {
8182type AcpModelInfo = { modelId : string ; name : string ; description ?: string } ;
8283type AcpModelState = { availableModels : AcpModelInfo [ ] ; currentModelId : string } ;
8384
85+ /** Advertised on `initialize._meta` so CrewCode can call compact instead of local summary-reset. */
86+ export const CREWCODER_SESSION_COMPACT_META = {
87+ method : "session/compact" ,
88+ preview : true ,
89+ editedSummary : true
90+ } as const ;
91+
92+ const EXT_METHODS = new Set ( [
93+ "session/set_model" ,
94+ "session/set_reasoning_effort" ,
95+ "session/set_approval_mode" ,
96+ "session/set_external_directories" ,
97+ "session/follow_up" ,
98+ "session/compact"
99+ ] ) ;
100+
84101const PERMISSION_OPTIONS : PermissionOption [ ] = [
85102 { optionId : "allow_once" , name : "Allow" , kind : "allow_once" } ,
86103 { optionId : "allow_always" , name : "Always allow" , kind : "allow_always" } ,
@@ -120,7 +137,8 @@ export class CrewCoderAcpAgent implements Agent {
120137 embeddedContext : false
121138 }
122139 } ,
123- authMethods : [ ]
140+ authMethods : [ ] ,
141+ _meta : { "crewcoder/sessionCompact" : CREWCODER_SESSION_COMPACT_META }
124142 } ;
125143 }
126144
@@ -179,10 +197,14 @@ export class CrewCoderAcpAgent implements Agent {
179197 * respawning CrewCoder.
180198 */
181199 async extMethod ( method : string , params : Record < string , unknown > ) : Promise < Record < string , unknown > > {
182- if ( method !== "session/set_model" && method !== "session/set_reasoning_effort" && method !== "session/set_approval_mode" && method !== "session/set_external_directories" && method !== "session/follow_up" ) throw RequestError . methodNotFound ( method ) ;
200+ if ( ! EXT_METHODS . has ( method ) ) throw RequestError . methodNotFound ( method ) ;
183201 const sessionId = typeof params . sessionId === "string" ? params . sessionId : "" ;
184202 const session = this . session ( sessionId ) ;
185203
204+ if ( method === "session/compact" ) {
205+ return this . compactSession ( session , params ) ;
206+ }
207+
186208 if ( method === "session/follow_up" ) {
187209 const message = typeof params . message === "string" ? params . message . trim ( ) : "" ;
188210 if ( ! message ) throw RequestError . invalidParams ( { reason : "message is required" } ) ;
@@ -238,6 +260,51 @@ export class CrewCoderAcpAgent implements Agent {
238260 return { } ;
239261 }
240262
263+ private async compactSession ( session : AcpSession , params : Record < string , unknown > ) : Promise < Record < string , unknown > > {
264+ if ( session . abort ) {
265+ throw RequestError . invalidParams ( { reason : "Cannot compact while a prompt is running. Wait for the current turn to finish." } ) ;
266+ }
267+ if ( ! session . started ) {
268+ throw RequestError . invalidParams ( { reason : "session has no durable transcript yet" } ) ;
269+ }
270+ const preview = params . preview === true ;
271+ const summary = typeof params . summary === "string" ? params . summary : undefined ;
272+ const emit = async ( event : AgentEvent ) : Promise < void > => {
273+ const update = translateEvent ( event ) ;
274+ if ( update ) await this . conn . sessionUpdate ( { sessionId : session . sessionId , update : update as unknown as SessionUpdate } ) ;
275+ } ;
276+ try {
277+ const result = await compactDurableSession ( {
278+ sessionId : session . sessionId ,
279+ modelClient : this . modelClientFor ( session ) ,
280+ cwd : session . cwd ,
281+ preview,
282+ editedSummary : summary ,
283+ emit,
284+ automatic : false
285+ } ) ;
286+ return {
287+ compacted : result . compacted ,
288+ preview : result . preview ,
289+ edited : result . edited ,
290+ compactionId : result . compactionId ,
291+ source : result . source ,
292+ fallbackReason : result . fallbackReason ,
293+ originalMessageCount : result . originalMessageCount ,
294+ retainedMessageCount : result . retainedMessageCount ,
295+ summary : result . summary
296+ } ;
297+ } catch ( error ) {
298+ throw RequestError . internalError ( { message : error instanceof Error ? error . message : String ( error ) } ) ;
299+ }
300+ }
301+
302+ private modelClientFor ( session : AcpSession ) : ProviderModelClient | HeuristicModelClient {
303+ return this . options . heuristic
304+ ? new HeuristicModelClient ( )
305+ : new ProviderModelClient ( session . providerId , session . cwd , session . model , undefined , session . reasoningEffort ) ;
306+ }
307+
241308 async cancel ( params : CancelNotification ) : Promise < void > {
242309 const session = this . sessions . get ( params . sessionId ) ;
243310 if ( ! session ) return ;
0 commit comments