Fix session ID handling and cleanup in copilot-sdk

Co-authored-by: htekdev <100806365+htekdev@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-01-28 21:51:55 +00:00
parent 0e28bf70cd
commit 119cac7543
3 changed files with 13487 additions and 40 deletions

13441
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -141,12 +141,18 @@ export async function runCopilotCliAgent(params: {
const text = result.text?.trim();
const payloads = text ? [{ text }] : undefined;
// When resuming a session, the SDK's sessionId should be authoritative.
// For new sessions, fall back to params.sessionId if SDK doesn't return one.
const resolvedSessionId = params.cliSessionId
? result.sessionId // Resuming: use SDK's session ID
: (result.sessionId ?? params.sessionId ?? ""); // New: SDK or fallback
return {
payloads,
meta: {
durationMs: Date.now() - started,
agentMeta: {
sessionId: result.sessionId ?? params.sessionId ?? "",
sessionId: resolvedSessionId,
provider,
model: modelId,
},

View File

@ -18,13 +18,7 @@ import { isCopilotCliInstalled, readCopilotAuthStatusCached } from "./copilot-cr
const log = createSubsystemLogger("agents/copilot-sdk");
// Re-export SDK types that consumers may need.
export type {
CopilotClient,
CopilotClientOptions,
CopilotSession,
SessionConfig,
SessionEvent,
};
export type { CopilotClient, CopilotClientOptions, CopilotSession, SessionConfig, SessionEvent };
/**
* Options for creating a Moltbot-configured Copilot client.
@ -149,10 +143,12 @@ export type CopilotAgentResult = {
* Creates a client, starts a session, sends the prompt, waits for completion,
* and cleans up. For multi-turn conversations, pass the returned `sessionId`
* back in subsequent calls.
*
* Note: When resuming a session, model and system prompt settings from the
* original session are preserved. New configuration values are not applied
* during resumption.
*/
export async function runCopilotAgent(
params: RunCopilotAgentParams,
): Promise<CopilotAgentResult> {
export async function runCopilotAgent(params: RunCopilotAgentParams): Promise<CopilotAgentResult> {
const started = Date.now();
const events: SessionEvent[] = [];
let finalText = "";
@ -163,17 +159,20 @@ export async function runCopilotAgent(
env: params.env,
});
let session: CopilotSession | null = null;
let unsubscribe: (() => void) | null = null;
try {
// Start the client
await client.start();
// Configure session
// Configure session for new sessions
const sessionConfig: SessionConfig = {
model: params.model,
sessionId: params.sessionId,
};
// Add system message if provided
// Add system message if provided (only applies to new sessions)
if (params.systemPrompt) {
sessionConfig.systemMessage = {
mode: "append",
@ -182,7 +181,9 @@ export async function runCopilotAgent(
}
// Create or resume session
let session: CopilotSession;
// Note: When resuming, the SDK preserves the original session's model/prompt.
// The ResumeSessionConfig type only supports tools, provider, streaming,
// onPermissionRequest, mcpServers, customAgents, skillDirectories, disabledSkills.
if (params.sessionId) {
session = await client.resumeSession(params.sessionId, {
streaming: true,
@ -194,7 +195,7 @@ export async function runCopilotAgent(
const sessionId = session.sessionId;
// Set up event handler
const unsubscribe = session.on((event: SessionEvent) => {
unsubscribe = session.on((event: SessionEvent) => {
events.push(event);
params.onEvent?.(event);
@ -207,34 +208,33 @@ export async function runCopilotAgent(
}
});
try {
// Send the message and wait for completion
const result = await session.sendAndWait(
{ prompt: params.prompt },
params.timeoutMs ?? 120000,
);
// Send the message and wait for completion
const result = await session.sendAndWait({ prompt: params.prompt }, params.timeoutMs ?? 120000);
// Extract text from result if available
if (result?.data?.content && typeof result.data.content === "string") {
finalText = result.data.content;
}
return {
text: finalText,
sessionId,
events,
durationMs: Date.now() - started,
};
} finally {
unsubscribe();
// Note: We don't destroy the session if sessionId was provided for resumption
if (!params.sessionId) {
await session.destroy().catch(() => {
// Ignore cleanup errors
});
}
// Extract text from result if available
if (result?.data?.content && typeof result.data.content === "string") {
finalText = result.data.content;
}
return {
text: finalText,
sessionId,
events,
durationMs: Date.now() - started,
};
} finally {
// Clean up in reverse order: unsubscribe, destroy session (if new), stop client
if (unsubscribe) {
unsubscribe();
}
// Only destroy session if we created a new one (not resuming)
if (session && !params.sessionId) {
await session.destroy().catch(() => {
// Ignore cleanup errors
});
}
// Always stop the client
await client.stop().catch(() => {
// Ignore cleanup errors