Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions apps/twig/src/main/services/agent/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,6 @@ interface ManagedSession {
promptPending: boolean;
pendingContext?: string;
configOptions?: SessionConfigOption[];
sessionId: string;
}

function getClaudeCliPath(): string {
Expand Down Expand Up @@ -419,7 +418,6 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
repoPath: rawRepoPath,
credentials,
logUrl,
sessionId: existingSessionId,
adapter,
additionalDirectories,
permissionMode,
Expand Down Expand Up @@ -534,19 +532,22 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
configOptions = loadResponse.configOptions ?? undefined;
agentSessionId = config.sessionId;
} else if (isReconnect && adapter !== "codex") {
if (!config.sessionId) {
throw new Error("Cannot resume session without sessionId");
}
const systemPrompt = this.buildPostHogSystemPrompt(credentials);
const resumeResponse = await connection.extMethod(
"_posthog/session/resume",
{
sessionId: taskRunId,
sessionId: config.sessionId,
cwd: repoPath,
mcpServers,
_meta: {
...(logUrl && {
persistence: { taskId, runId: taskRunId, logUrl },
}),
taskRunId,
...(existingSessionId && { sessionId: existingSessionId }),
sessionId: config.sessionId,
systemPrompt,
...(permissionMode && { permissionMode }),
...(additionalDirectories?.length && {
Expand All @@ -563,7 +564,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
}
| undefined;
configOptions = resumeMeta?.configOptions;
agentSessionId = (resumeResponse?.sessionId as string) ?? taskRunId;
agentSessionId = config.sessionId;
} else {
const systemPrompt = this.buildPostHogSystemPrompt(credentials);
const newSessionResponse = await connection.newSession({
Expand Down Expand Up @@ -600,7 +601,6 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
needsRecreation: false,
promptPending: false,
configOptions,
sessionId: agentSessionId,
};

this.sessions.set(taskRunId, session);
Expand Down Expand Up @@ -706,7 +706,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {

try {
const result = await session.clientSideConnection.prompt({
sessionId: session.sessionId,
sessionId: session.config.sessionId!,
prompt: finalPrompt,
});
return {
Expand All @@ -718,7 +718,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
log.warn("Auth error during prompt, recreating session", { sessionId });
session = await this.recreateSession(sessionId);
const result = await session.clientSideConnection.prompt({
sessionId: session.sessionId,
sessionId: session.config.sessionId!,
prompt: finalPrompt,
});
return {
Expand Down Expand Up @@ -762,7 +762,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {

try {
await session.clientSideConnection.cancel({
sessionId: session.sessionId,
sessionId: session.config.sessionId!,
_meta: reason ? { interruptReason: reason } : undefined,
});
if (reason) {
Expand Down Expand Up @@ -792,7 +792,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {

try {
const result = await session.clientSideConnection.setSessionConfigOption({
sessionId: session.sessionId,
sessionId: session.config.sessionId!,
configId,
value,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ function buildConversationItems(events: AcpMessage[]): ConversationItem[] {

currentTurn = {
type: "turn",
id: `turn-${msg.id}`,
id: `turn-${event.ts}-${msg.id}`,
promptId: msg.id,
userContent,
items: [],
Expand Down Expand Up @@ -496,16 +496,22 @@ function processSessionUpdate(turn: Turn, update: SessionUpdate) {
turn.items.push(update);
break;

// Handle custom session updates
default: {
// Check for our custom session update types
const customUpdate = update as unknown as {
sessionUpdate: string;
content?: { type: string; text?: string };
status?: string;
errorType?: string;
message?: string;
};
if (
if (customUpdate.sessionUpdate === "agent_message") {
if (customUpdate.content?.type === "text") {
appendTextChunk(turn, {
sessionUpdate: "agent_message_chunk" as const,
content: customUpdate.content as { type: "text"; text: string },
});
}
} else if (
customUpdate.sessionUpdate === "status" ||
customUpdate.sessionUpdate === "error"
) {
Expand Down
14 changes: 7 additions & 7 deletions packages/agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,28 +72,28 @@
},
"devDependencies": {
"@changesets/cli": "^2.27.8",
"@posthog/shared": "workspace:*",
"@twig/git": "workspace:*",
"@types/bun": "latest",
"@types/tar": "^6.1.13",
"minimatch": "^10.0.3",
"@posthog/shared": "workspace:*",
"@twig/git": "workspace:*",
"msw": "^2.12.7",
"tsup": "^8.5.1",
"tsx": "^4.20.6",
"typescript": "^5.5.0",
"vitest": "^2.1.8"
},
"dependencies": {
"@agentclientprotocol/sdk": "^0.14.0",
"@anthropic-ai/claude-agent-sdk": "0.2.42",
"@anthropic-ai/sdk": "^0.71.0",
"@hono/node-server": "^1.19.9",
"@modelcontextprotocol/sdk": "^1.25.3",
"@opentelemetry/api-logs": "^0.208.0",
"@opentelemetry/exporter-logs-otlp-http": "^0.208.0",
"@opentelemetry/resources": "^2.0.0",
"@opentelemetry/sdk-logs": "^0.208.0",
"@opentelemetry/semantic-conventions": "^1.28.0",
"@agentclientprotocol/sdk": "^0.14.0",
"@anthropic-ai/claude-agent-sdk": "0.2.12",
"@anthropic-ai/sdk": "^0.71.0",
"@hono/node-server": "^1.19.9",
"@modelcontextprotocol/sdk": "^1.25.3",
"@types/jsonwebtoken": "^9.0.10",
"commander": "^14.0.2",
"diff": "^8.0.2",
Expand Down
63 changes: 35 additions & 28 deletions packages/agent/src/adapters/claude/claude-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
this.checkAuthStatus();

const meta = params._meta as NewSessionMeta | undefined;
const internalSessionId = uuidv7();
const sessionId = uuidv7();
const permissionMode: TwigExecutionMode =
meta?.permissionMode &&
TWIG_EXECUTION_MODES.includes(meta.permissionMode as TwigExecutionMode)
Expand All @@ -151,11 +151,13 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
cwd: params.cwd,
mcpServers,
permissionMode,
canUseTool: this.createCanUseTool(internalSessionId),
canUseTool: this.createCanUseTool(sessionId),
logger: this.logger,
systemPrompt: buildSystemPrompt(meta?.systemPrompt),
userProvidedOptions: meta?.claudeCode?.options,
onModeChange: this.createOnModeChange(internalSessionId),
sessionId,
isResume: false,
onModeChange: this.createOnModeChange(sessionId),
onProcessSpawned: this.processCallbacks?.onProcessSpawned,
onProcessExited: this.processCallbacks?.onProcessExited,
});
Expand All @@ -164,29 +166,35 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
const q = query({ prompt: input, options });

const session = this.createSession(
internalSessionId,
sessionId,
q,
input,
permissionMode,
params.cwd,
options.abortController as AbortController,
);
session.taskRunId = meta?.taskRunId;
this.registerPersistence(
internalSessionId,
meta as Record<string, unknown>,
);
this.registerPersistence(sessionId, meta as Record<string, unknown>);

if (meta?.taskRunId) {
await this.client.extNotification("_posthog/sdk_session", {
taskRunId: meta.taskRunId,
sessionId,
adapter: "claude",
});
}

const modelOptions = await this.getModelConfigOptions();
session.modelId = modelOptions.currentModelId;
await this.trySetModel(q, modelOptions.currentModelId);

this.sendAvailableCommandsUpdate(
internalSessionId,
sessionId,
await getAvailableSlashCommands(q),
);

return {
sessionId: internalSessionId,
sessionId,
configOptions: await this.buildConfigOptions(modelOptions),
};
}
Expand All @@ -198,12 +206,15 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
async resumeSession(
params: LoadSessionRequest,
): Promise<LoadSessionResponse> {
const { sessionId: internalSessionId } = params;
if (this.sessionId === internalSessionId) {
const meta = params._meta as NewSessionMeta | undefined;
const sessionId = meta?.sessionId;
if (!sessionId) {
throw new Error("Cannot resume session without sessionId");
}
if (this.sessionId === sessionId) {
return {};
}

const meta = params._meta as NewSessionMeta | undefined;
const mcpServers = parseMcpServers(params);
await fetchMcpToolMetadata(mcpServers, this.logger);

Expand All @@ -214,27 +225,21 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
: "default";

const { query: q, session } = await this.initializeQuery({
internalSessionId,
cwd: params.cwd,
permissionMode,
mcpServers,
systemPrompt: buildSystemPrompt(meta?.systemPrompt),
userProvidedOptions: meta?.claudeCode?.options,
sessionId: meta?.sessionId,
sessionId,
isResume: true,
additionalDirectories: meta?.claudeCode?.options?.additionalDirectories,
});

session.taskRunId = meta?.taskRunId;
if (meta?.sessionId) {
session.sessionId = meta.sessionId;
}

this.registerPersistence(
internalSessionId,
meta as Record<string, unknown>,
);
this.registerPersistence(sessionId, meta as Record<string, unknown>);
this.sendAvailableCommandsUpdate(
internalSessionId,
sessionId,
await getAvailableSlashCommands(q),
);

Expand Down Expand Up @@ -322,13 +327,13 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
}

private async initializeQuery(config: {
internalSessionId: string;
cwd: string;
permissionMode: TwigExecutionMode;
mcpServers: ReturnType<typeof parseMcpServers>;
userProvidedOptions?: Options;
systemPrompt?: Options["systemPrompt"];
sessionId?: string;
sessionId: string;
isResume: boolean;
additionalDirectories?: string[];
}): Promise<{
query: Query;
Expand All @@ -341,13 +346,14 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
cwd: config.cwd,
mcpServers: config.mcpServers,
permissionMode: config.permissionMode,
canUseTool: this.createCanUseTool(config.internalSessionId),
canUseTool: this.createCanUseTool(config.sessionId),
logger: this.logger,
systemPrompt: config.systemPrompt,
userProvidedOptions: config.userProvidedOptions,
sessionId: config.sessionId,
isResume: config.isResume,
additionalDirectories: config.additionalDirectories,
onModeChange: this.createOnModeChange(config.internalSessionId),
onModeChange: this.createOnModeChange(config.sessionId),
onProcessSpawned: this.processCallbacks?.onProcessSpawned,
onProcessExited: this.processCallbacks?.onProcessExited,
});
Expand All @@ -356,7 +362,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
const abortController = options.abortController as AbortController;

const session = this.createSession(
config.internalSessionId,
config.sessionId,
q,
input,
config.permissionMode,
Expand Down Expand Up @@ -596,6 +602,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {

case "tool_progress":
case "auth_status":
case "tool_use_summary":
return null;

default:
Expand Down
12 changes: 1 addition & 11 deletions packages/agent/src/adapters/claude/conversion/sdk-to-acp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,20 +328,10 @@ export async function handleSystemMessage(
message: any,
context: MessageHandlerContext,
): Promise<void> {
const { session, sessionId, client, logger } = context;
const { sessionId, client, logger } = context;

switch (message.subtype) {
case "init":
if (message.session_id && session && !session.sessionId) {
session.sessionId = message.session_id;
if (session.taskRunId) {
await client.extNotification("_posthog/sdk_session", {
taskRunId: session.taskRunId,
sessionId: message.session_id,
adapter: "claude",
});
}
}
break;
case "compact_boundary":
await client.extNotification("_posthog/compact_boundary", {
Expand Down
10 changes: 7 additions & 3 deletions packages/agent/src/adapters/claude/session/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export interface BuildOptionsParams {
logger: Logger;
systemPrompt?: Options["systemPrompt"];
userProvidedOptions?: Options;
sessionId?: string;
sessionId: string;
isResume: boolean;
additionalDirectories?: string[];
onModeChange?: OnModeChange;
onProcessSpawned?: (info: ProcessSpawnedInfo) => void;
Expand Down Expand Up @@ -213,7 +214,7 @@ export function buildSessionOptions(params: BuildOptionsParams): Options {
),
...(params.onProcessSpawned && {
spawnClaudeCodeProcess: buildSpawnWrapper(
params.sessionId ?? "unknown",
params.sessionId,
params.onProcessSpawned,
params.onProcessExited,
),
Expand All @@ -224,8 +225,11 @@ export function buildSessionOptions(params: BuildOptionsParams): Options {
options.pathToClaudeCodeExecutable = process.env.CLAUDE_CODE_EXECUTABLE;
}

if (params.sessionId) {
if (params.isResume) {
options.resume = params.sessionId;
options.forkSession = false;
} else {
options.sessionId = params.sessionId;
}

if (params.additionalDirectories) {
Expand Down
1 change: 0 additions & 1 deletion packages/agent/src/adapters/claude/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export type Session = BaseSession & {
modelId?: string;
cwd: string;
taskRunId?: string;
sessionId?: string;
lastPlanFilePath?: string;
lastPlanContent?: string;
};
Expand Down
Loading
Loading