@posthog/agent 2.1.16 → 2.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/agent",
3
- "version": "2.1.16",
3
+ "version": "2.1.22",
4
4
  "repository": "https://github.com/PostHog/twig",
5
5
  "description": "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
6
6
  "exports": {
@@ -76,16 +76,16 @@
76
76
  "@twig/git": "1.0.0"
77
77
  },
78
78
  "dependencies": {
79
+ "@agentclientprotocol/sdk": "^0.14.0",
80
+ "@anthropic-ai/claude-agent-sdk": "0.2.42",
81
+ "@anthropic-ai/sdk": "^0.71.0",
82
+ "@hono/node-server": "^1.19.9",
83
+ "@modelcontextprotocol/sdk": "^1.25.3",
79
84
  "@opentelemetry/api-logs": "^0.208.0",
80
85
  "@opentelemetry/exporter-logs-otlp-http": "^0.208.0",
81
86
  "@opentelemetry/resources": "^2.0.0",
82
87
  "@opentelemetry/sdk-logs": "^0.208.0",
83
88
  "@opentelemetry/semantic-conventions": "^1.28.0",
84
- "@agentclientprotocol/sdk": "^0.14.0",
85
- "@anthropic-ai/claude-agent-sdk": "0.2.12",
86
- "@anthropic-ai/sdk": "^0.71.0",
87
- "@hono/node-server": "^1.19.9",
88
- "@modelcontextprotocol/sdk": "^1.25.3",
89
89
  "@types/jsonwebtoken": "^9.0.10",
90
90
  "commander": "^14.0.2",
91
91
  "diff": "^8.0.2",
@@ -137,7 +137,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
137
137
  this.checkAuthStatus();
138
138
 
139
139
  const meta = params._meta as NewSessionMeta | undefined;
140
- const internalSessionId = uuidv7();
140
+ const sessionId = uuidv7();
141
141
  const permissionMode: TwigExecutionMode =
142
142
  meta?.permissionMode &&
143
143
  TWIG_EXECUTION_MODES.includes(meta.permissionMode as TwigExecutionMode)
@@ -151,11 +151,13 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
151
151
  cwd: params.cwd,
152
152
  mcpServers,
153
153
  permissionMode,
154
- canUseTool: this.createCanUseTool(internalSessionId),
154
+ canUseTool: this.createCanUseTool(sessionId),
155
155
  logger: this.logger,
156
156
  systemPrompt: buildSystemPrompt(meta?.systemPrompt),
157
157
  userProvidedOptions: meta?.claudeCode?.options,
158
- onModeChange: this.createOnModeChange(internalSessionId),
158
+ sessionId,
159
+ isResume: false,
160
+ onModeChange: this.createOnModeChange(sessionId),
159
161
  onProcessSpawned: this.processCallbacks?.onProcessSpawned,
160
162
  onProcessExited: this.processCallbacks?.onProcessExited,
161
163
  });
@@ -164,7 +166,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
164
166
  const q = query({ prompt: input, options });
165
167
 
166
168
  const session = this.createSession(
167
- internalSessionId,
169
+ sessionId,
168
170
  q,
169
171
  input,
170
172
  permissionMode,
@@ -172,21 +174,27 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
172
174
  options.abortController as AbortController,
173
175
  );
174
176
  session.taskRunId = meta?.taskRunId;
175
- this.registerPersistence(
176
- internalSessionId,
177
- meta as Record<string, unknown>,
178
- );
177
+ this.registerPersistence(sessionId, meta as Record<string, unknown>);
178
+
179
+ if (meta?.taskRunId) {
180
+ await this.client.extNotification("_posthog/sdk_session", {
181
+ taskRunId: meta.taskRunId,
182
+ sessionId,
183
+ adapter: "claude",
184
+ });
185
+ }
186
+
179
187
  const modelOptions = await this.getModelConfigOptions();
180
188
  session.modelId = modelOptions.currentModelId;
181
189
  await this.trySetModel(q, modelOptions.currentModelId);
182
190
 
183
191
  this.sendAvailableCommandsUpdate(
184
- internalSessionId,
192
+ sessionId,
185
193
  await getAvailableSlashCommands(q),
186
194
  );
187
195
 
188
196
  return {
189
- sessionId: internalSessionId,
197
+ sessionId,
190
198
  configOptions: await this.buildConfigOptions(modelOptions),
191
199
  };
192
200
  }
@@ -198,12 +206,15 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
198
206
  async resumeSession(
199
207
  params: LoadSessionRequest,
200
208
  ): Promise<LoadSessionResponse> {
201
- const { sessionId: internalSessionId } = params;
202
- if (this.sessionId === internalSessionId) {
209
+ const meta = params._meta as NewSessionMeta | undefined;
210
+ const sessionId = meta?.sessionId;
211
+ if (!sessionId) {
212
+ throw new Error("Cannot resume session without sessionId");
213
+ }
214
+ if (this.sessionId === sessionId) {
203
215
  return {};
204
216
  }
205
217
 
206
- const meta = params._meta as NewSessionMeta | undefined;
207
218
  const mcpServers = parseMcpServers(params);
208
219
  await fetchMcpToolMetadata(mcpServers, this.logger);
209
220
 
@@ -214,27 +225,21 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
214
225
  : "default";
215
226
 
216
227
  const { query: q, session } = await this.initializeQuery({
217
- internalSessionId,
218
228
  cwd: params.cwd,
219
229
  permissionMode,
220
230
  mcpServers,
221
231
  systemPrompt: buildSystemPrompt(meta?.systemPrompt),
222
232
  userProvidedOptions: meta?.claudeCode?.options,
223
- sessionId: meta?.sessionId,
233
+ sessionId,
234
+ isResume: true,
224
235
  additionalDirectories: meta?.claudeCode?.options?.additionalDirectories,
225
236
  });
226
237
 
227
238
  session.taskRunId = meta?.taskRunId;
228
- if (meta?.sessionId) {
229
- session.sessionId = meta.sessionId;
230
- }
231
239
 
232
- this.registerPersistence(
233
- internalSessionId,
234
- meta as Record<string, unknown>,
235
- );
240
+ this.registerPersistence(sessionId, meta as Record<string, unknown>);
236
241
  this.sendAvailableCommandsUpdate(
237
- internalSessionId,
242
+ sessionId,
238
243
  await getAvailableSlashCommands(q),
239
244
  );
240
245
 
@@ -322,13 +327,13 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
322
327
  }
323
328
 
324
329
  private async initializeQuery(config: {
325
- internalSessionId: string;
326
330
  cwd: string;
327
331
  permissionMode: TwigExecutionMode;
328
332
  mcpServers: ReturnType<typeof parseMcpServers>;
329
333
  userProvidedOptions?: Options;
330
334
  systemPrompt?: Options["systemPrompt"];
331
- sessionId?: string;
335
+ sessionId: string;
336
+ isResume: boolean;
332
337
  additionalDirectories?: string[];
333
338
  }): Promise<{
334
339
  query: Query;
@@ -341,13 +346,14 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
341
346
  cwd: config.cwd,
342
347
  mcpServers: config.mcpServers,
343
348
  permissionMode: config.permissionMode,
344
- canUseTool: this.createCanUseTool(config.internalSessionId),
349
+ canUseTool: this.createCanUseTool(config.sessionId),
345
350
  logger: this.logger,
346
351
  systemPrompt: config.systemPrompt,
347
352
  userProvidedOptions: config.userProvidedOptions,
348
353
  sessionId: config.sessionId,
354
+ isResume: config.isResume,
349
355
  additionalDirectories: config.additionalDirectories,
350
- onModeChange: this.createOnModeChange(config.internalSessionId),
356
+ onModeChange: this.createOnModeChange(config.sessionId),
351
357
  onProcessSpawned: this.processCallbacks?.onProcessSpawned,
352
358
  onProcessExited: this.processCallbacks?.onProcessExited,
353
359
  });
@@ -356,7 +362,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
356
362
  const abortController = options.abortController as AbortController;
357
363
 
358
364
  const session = this.createSession(
359
- config.internalSessionId,
365
+ config.sessionId,
360
366
  q,
361
367
  input,
362
368
  config.permissionMode,
@@ -596,6 +602,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
596
602
 
597
603
  case "tool_progress":
598
604
  case "auth_status":
605
+ case "tool_use_summary":
599
606
  return null;
600
607
 
601
608
  default:
@@ -328,20 +328,10 @@ export async function handleSystemMessage(
328
328
  message: any,
329
329
  context: MessageHandlerContext,
330
330
  ): Promise<void> {
331
- const { session, sessionId, client, logger } = context;
331
+ const { sessionId, client, logger } = context;
332
332
 
333
333
  switch (message.subtype) {
334
334
  case "init":
335
- if (message.session_id && session && !session.sessionId) {
336
- session.sessionId = message.session_id;
337
- if (session.taskRunId) {
338
- await client.extNotification("_posthog/sdk_session", {
339
- taskRunId: session.taskRunId,
340
- sessionId: message.session_id,
341
- adapter: "claude",
342
- });
343
- }
344
- }
345
335
  break;
346
336
  case "compact_boundary":
347
337
  await client.extNotification("_posthog/compact_boundary", {
@@ -27,7 +27,8 @@ export interface BuildOptionsParams {
27
27
  logger: Logger;
28
28
  systemPrompt?: Options["systemPrompt"];
29
29
  userProvidedOptions?: Options;
30
- sessionId?: string;
30
+ sessionId: string;
31
+ isResume: boolean;
31
32
  additionalDirectories?: string[];
32
33
  onModeChange?: OnModeChange;
33
34
  onProcessSpawned?: (info: ProcessSpawnedInfo) => void;
@@ -175,7 +176,22 @@ function buildSpawnWrapper(
175
176
  };
176
177
  }
177
178
 
179
+ function ensureLocalSettings(cwd: string): void {
180
+ const claudeDir = path.join(cwd, ".claude");
181
+ const localSettingsPath = path.join(claudeDir, "settings.local.json");
182
+ try {
183
+ if (!fs.existsSync(localSettingsPath)) {
184
+ fs.mkdirSync(claudeDir, { recursive: true });
185
+ fs.writeFileSync(localSettingsPath, "{}\n", { flag: "wx" });
186
+ }
187
+ } catch {
188
+ // Best-effort — don't fail session creation if we can't write
189
+ }
190
+ }
191
+
178
192
  export function buildSessionOptions(params: BuildOptionsParams): Options {
193
+ ensureLocalSettings(params.cwd);
194
+
179
195
  const options: Options = {
180
196
  ...params.userProvidedOptions,
181
197
  systemPrompt: params.systemPrompt ?? buildSystemPrompt(),
@@ -198,7 +214,7 @@ export function buildSessionOptions(params: BuildOptionsParams): Options {
198
214
  ),
199
215
  ...(params.onProcessSpawned && {
200
216
  spawnClaudeCodeProcess: buildSpawnWrapper(
201
- params.sessionId ?? "unknown",
217
+ params.sessionId,
202
218
  params.onProcessSpawned,
203
219
  params.onProcessExited,
204
220
  ),
@@ -209,8 +225,11 @@ export function buildSessionOptions(params: BuildOptionsParams): Options {
209
225
  options.pathToClaudeCodeExecutable = process.env.CLAUDE_CODE_EXECUTABLE;
210
226
  }
211
227
 
212
- if (params.sessionId) {
228
+ if (params.isResume) {
213
229
  options.resume = params.sessionId;
230
+ options.forkSession = false;
231
+ } else {
232
+ options.sessionId = params.sessionId;
214
233
  }
215
234
 
216
235
  if (params.additionalDirectories) {
@@ -29,7 +29,6 @@ export type Session = BaseSession & {
29
29
  modelId?: string;
30
30
  cwd: string;
31
31
  taskRunId?: string;
32
- sessionId?: string;
33
32
  lastPlanFilePath?: string;
34
33
  lastPlanContent?: string;
35
34
  };
@@ -95,6 +95,11 @@ export function createMockQuery(
95
95
  .fn()
96
96
  .mockResolvedValue({ added: [], removed: [], errors: {} }),
97
97
  streamInput: vi.fn().mockResolvedValue(undefined),
98
+ close: vi.fn(),
99
+ initializationResult: vi.fn().mockResolvedValue({}),
100
+ reconnectMcpServer: vi.fn().mockResolvedValue(undefined),
101
+ toggleMcpServer: vi.fn().mockResolvedValue(undefined),
102
+ stopTask: vi.fn().mockResolvedValue(undefined),
98
103
  [Symbol.asyncDispose]: vi.fn().mockResolvedValue(undefined),
99
104
  _abortController: abortController,
100
105
  _mockHelpers: {
@@ -158,6 +163,7 @@ export function createSuccessResult(
158
163
  is_error: false,
159
164
  num_turns: 1,
160
165
  result: "Done",
166
+ stop_reason: null,
161
167
  total_cost_usd: 0.01,
162
168
  usage: {
163
169
  input_tokens: 100,
@@ -190,6 +196,7 @@ export function createErrorResult(
190
196
  duration_ms: 100,
191
197
  duration_api_ms: 50,
192
198
  num_turns: 1,
199
+ stop_reason: null,
193
200
  total_cost_usd: 0.01,
194
201
  usage: {
195
202
  input_tokens: 100,