@posthog/agent 2.1.114 → 2.1.118

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.114",
3
+ "version": "2.1.118",
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": {
@@ -71,8 +71,8 @@
71
71
  "tsx": "^4.20.6",
72
72
  "typescript": "^5.5.0",
73
73
  "vitest": "^2.1.8",
74
- "@twig/git": "1.0.0",
75
- "@posthog/shared": "1.0.0"
74
+ "@posthog/shared": "1.0.0",
75
+ "@twig/git": "1.0.0"
76
76
  },
77
77
  "dependencies": {
78
78
  "@agentclientprotocol/sdk": "^0.14.0",
@@ -195,7 +195,7 @@ function createClaudeConnection(config: AcpConnectionConfig): AcpConnection {
195
195
 
196
196
  let agent: ClaudeAcpAgent | null = null;
197
197
  const agentConnection = new AgentSideConnection((client) => {
198
- agent = new ClaudeAcpAgent(client, logWriter, config.processCallbacks);
198
+ agent = new ClaudeAcpAgent(client, config.processCallbacks);
199
199
  logger.info(`Created ${agent.adapterName} agent`);
200
200
  return agent;
201
201
  }, agentStream);
@@ -31,8 +31,6 @@ import {
31
31
  } from "@anthropic-ai/claude-agent-sdk";
32
32
  import { v7 as uuidv7 } from "uuid";
33
33
  import packageJson from "../../../package.json" with { type: "json" };
34
- import type { SessionContext } from "../../otel-log-writer.js";
35
- import type { SessionLogWriter } from "../../session-log-writer.js";
36
34
  import { unreachable, withTimeout } from "../../utils/common.js";
37
35
  import { Logger } from "../../utils/logger.js";
38
36
  import { Pushable } from "../../utils/streams.js";
@@ -79,17 +77,11 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
79
77
  toolUseCache: ToolUseCache;
80
78
  backgroundTerminals: { [key: string]: BackgroundTerminal } = {};
81
79
  clientCapabilities?: ClientCapabilities;
82
- private logWriter?: SessionLogWriter;
83
80
  private options?: ClaudeAcpAgentOptions;
84
81
  private lastSentConfigOptions?: SessionConfigOption[];
85
82
 
86
- constructor(
87
- client: AgentSideConnection,
88
- logWriter?: SessionLogWriter,
89
- options?: ClaudeAcpAgentOptions,
90
- ) {
83
+ constructor(client: AgentSideConnection, options?: ClaudeAcpAgentOptions) {
91
84
  super(client);
92
- this.logWriter = logWriter;
93
85
  this.options = options;
94
86
  this.toolUseCache = {};
95
87
  this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
@@ -139,7 +131,14 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
139
131
  this.checkAuthStatus();
140
132
 
141
133
  const meta = params._meta as NewSessionMeta | undefined;
134
+ const taskId = meta?.persistence?.taskId;
142
135
  const sessionId = uuidv7();
136
+ this.logger.info("Creating new session", {
137
+ sessionId,
138
+ taskId,
139
+ taskRunId: meta?.taskRunId,
140
+ cwd: params.cwd,
141
+ });
143
142
  const permissionMode: TwigExecutionMode =
144
143
  meta?.permissionMode &&
145
144
  TWIG_EXECUTION_MODES.includes(meta.permissionMode as TwigExecutionMode)
@@ -177,7 +176,6 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
177
176
  options.abortController as AbortController,
178
177
  );
179
178
  session.taskRunId = meta?.taskRunId;
180
- this.registerPersistence(sessionId, meta as Record<string, unknown>);
181
179
 
182
180
  if (meta?.taskRunId) {
183
181
  await this.client.extNotification("_posthog/sdk_session", {
@@ -218,6 +216,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
218
216
  params: LoadSessionRequest,
219
217
  ): Promise<LoadSessionResponse> {
220
218
  const meta = params._meta as NewSessionMeta | undefined;
219
+ const taskId = meta?.persistence?.taskId;
221
220
  const sessionId = meta?.sessionId;
222
221
  if (!sessionId) {
223
222
  throw new Error("Cannot resume session without sessionId");
@@ -226,6 +225,13 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
226
225
  return {};
227
226
  }
228
227
 
228
+ this.logger.info("Resuming session", {
229
+ sessionId,
230
+ taskId,
231
+ taskRunId: meta?.taskRunId,
232
+ cwd: params.cwd,
233
+ });
234
+
229
235
  const mcpServers = parseMcpServers(params);
230
236
 
231
237
  const permissionMode: TwigExecutionMode =
@@ -245,20 +251,42 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
245
251
  additionalDirectories: meta?.claudeCode?.options?.additionalDirectories,
246
252
  });
247
253
 
248
- session.taskRunId = meta?.taskRunId;
254
+ this.logger.info("Session query initialized, awaiting resumption", {
255
+ sessionId,
256
+ taskId,
257
+ taskRunId: meta?.taskRunId,
258
+ });
249
259
 
250
- this.registerPersistence(sessionId, meta as Record<string, unknown>);
260
+ session.taskRunId = meta?.taskRunId;
251
261
 
252
- // Validate the resumed session is alive. For stale sessions this throws
262
+ // Check the resumed session is alive. For stale sessions this throws
253
263
  // (e.g. "No conversation found"), preventing a broken session.
254
- const validation = await withTimeout(
255
- q.initializationResult(),
256
- SESSION_VALIDATION_TIMEOUT_MS,
257
- );
258
- if (validation.result === "timeout") {
259
- throw new Error("Session validation timed out");
264
+ try {
265
+ const result = await withTimeout(
266
+ q.initializationResult(),
267
+ SESSION_VALIDATION_TIMEOUT_MS,
268
+ );
269
+ if (result.result === "timeout") {
270
+ throw new Error(
271
+ `Session resumption timed out for sessionId=${sessionId}`,
272
+ );
273
+ }
274
+ } catch (err) {
275
+ this.logger.error("Session resumption failed", {
276
+ sessionId,
277
+ taskId,
278
+ taskRunId: meta?.taskRunId,
279
+ error: err instanceof Error ? err.message : String(err),
280
+ });
281
+ throw err;
260
282
  }
261
283
 
284
+ this.logger.info("Session resumed successfully", {
285
+ sessionId,
286
+ taskId,
287
+ taskRunId: meta?.taskRunId,
288
+ });
289
+
262
290
  // Deferred: slash commands + MCP metadata (not needed to return configOptions)
263
291
  this.deferBackgroundFetches(q, sessionId);
264
292
 
@@ -527,16 +555,6 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
527
555
  });
528
556
  }
529
557
 
530
- private registerPersistence(
531
- sessionId: string,
532
- meta: Record<string, unknown> | undefined,
533
- ) {
534
- const persistence = meta?.persistence as SessionContext | undefined;
535
- if (persistence && this.logWriter) {
536
- this.logWriter.register(sessionId, persistence);
537
- }
538
- }
539
-
540
558
  private sendAvailableCommandsUpdate(
541
559
  sessionId: string,
542
560
  availableCommands: AvailableCommand[],
@@ -56,6 +56,7 @@ export type NewSessionMeta = {
56
56
  systemPrompt?: unknown;
57
57
  sessionId?: string;
58
58
  permissionMode?: string;
59
+ persistence?: { taskId?: string; runId?: string; logUrl?: string };
59
60
  claudeCode?: {
60
61
  options?: Options;
61
62
  };
@@ -651,7 +651,10 @@ Important:
651
651
 
652
652
  private configureEnvironment(): void {
653
653
  const { apiKey, apiUrl, projectId } = this.config;
654
- const gatewayUrl = process.env.LLM_GATEWAY_URL || getLlmGatewayUrl(apiUrl);
654
+ const product =
655
+ this.config.mode === "background" ? "background_agents" : "twig";
656
+ const gatewayUrl =
657
+ process.env.LLM_GATEWAY_URL || getLlmGatewayUrl(apiUrl, product);
655
658
  const openaiBaseUrl = gatewayUrl.endsWith("/v1")
656
659
  ? gatewayUrl
657
660
  : `${gatewayUrl}/v1`;
@@ -50,11 +50,15 @@ export class SessionLogWriter {
50
50
 
51
51
  async flushAll(): Promise<void> {
52
52
  const sessionIds = [...this.sessions.keys()];
53
- const pendingCounts = sessionIds.map((id) => ({
54
- id,
55
- pending: this.pendingEntries.get(id)?.length ?? 0,
56
- messages: this.messageCounts.get(id) ?? 0,
57
- }));
53
+ const pendingCounts = sessionIds.map((id) => {
54
+ const session = this.sessions.get(id);
55
+ return {
56
+ taskId: session?.context.taskId,
57
+ runId: session?.context.runId,
58
+ pending: this.pendingEntries.get(id)?.length ?? 0,
59
+ messages: this.messageCounts.get(id) ?? 0,
60
+ };
61
+ });
58
62
  this.logger.info("flushAll called", {
59
63
  sessions: sessionIds.length,
60
64
  pending: pendingCounts,
@@ -73,8 +77,8 @@ export class SessionLogWriter {
73
77
  }
74
78
 
75
79
  this.logger.info("Session registered", {
76
- sessionId,
77
80
  taskId: context.taskId,
81
+ runId: context.runId,
78
82
  });
79
83
  this.sessions.set(sessionId, { context });
80
84
 
@@ -113,7 +117,11 @@ export class SessionLogWriter {
113
117
  const count = (this.messageCounts.get(sessionId) ?? 0) + 1;
114
118
  this.messageCounts.set(sessionId, count);
115
119
  if (count % 10 === 1) {
116
- this.logger.info("Messages received", { count, sessionId });
120
+ this.logger.info("Messages received", {
121
+ count,
122
+ taskId: session.context.taskId,
123
+ runId: session.context.runId,
124
+ });
117
125
  }
118
126
 
119
127
  try {
@@ -153,7 +161,8 @@ export class SessionLogWriter {
153
161
  }
154
162
  } catch {
155
163
  this.logger.warn("Failed to parse raw line for persistence", {
156
- sessionId,
164
+ taskId: session.context.taskId,
165
+ runId: session.context.runId,
157
166
  lineLength: line.length,
158
167
  });
159
168
  }
@@ -172,7 +181,8 @@ export class SessionLogWriter {
172
181
  const pending = this.pendingEntries.get(sessionId);
173
182
  if (!this.posthogAPI || !pending?.length) {
174
183
  this.logger.info("flush: nothing to persist", {
175
- sessionId,
184
+ taskId: session.context.taskId,
185
+ runId: session.context.runId,
176
186
  hasPosthogAPI: !!this.posthogAPI,
177
187
  pendingCount: pending?.length ?? 0,
178
188
  });
@@ -196,7 +206,8 @@ export class SessionLogWriter {
196
206
  );
197
207
  this.retryCounts.set(sessionId, 0);
198
208
  this.logger.info("Flushed session logs", {
199
- sessionId,
209
+ taskId: session.context.taskId,
210
+ runId: session.context.runId,
200
211
  entryCount: pending.length,
201
212
  });
202
213
  } catch (error) {
@@ -206,7 +217,11 @@ export class SessionLogWriter {
206
217
  if (retryCount >= SessionLogWriter.MAX_FLUSH_RETRIES) {
207
218
  this.logger.error(
208
219
  `Dropping ${pending.length} session log entries after ${retryCount} failed flush attempts`,
209
- { sessionId, error },
220
+ {
221
+ taskId: session.context.taskId,
222
+ runId: session.context.runId,
223
+ error,
224
+ },
210
225
  );
211
226
  this.retryCounts.set(sessionId, 0);
212
227
  } else {
@@ -316,7 +331,12 @@ export class SessionLogWriter {
316
331
  try {
317
332
  fs.appendFileSync(logPath, `${JSON.stringify(entry)}\n`);
318
333
  } catch (error) {
319
- this.logger.warn("Failed to write to local cache", { logPath, error });
334
+ this.logger.warn("Failed to write to local cache", {
335
+ taskId: session.context.taskId,
336
+ runId: session.context.runId,
337
+ logPath,
338
+ error,
339
+ });
320
340
  }
321
341
  }
322
342
  }
@@ -1,18 +1,23 @@
1
- export function getLlmGatewayUrl(posthogHost: string): string {
1
+ export type GatewayProduct = "twig" | "background_agents";
2
+
3
+ export function getLlmGatewayUrl(
4
+ posthogHost: string,
5
+ product: GatewayProduct = "twig",
6
+ ): string {
2
7
  const url = new URL(posthogHost);
3
8
  const hostname = url.hostname;
4
9
 
5
10
  // Local development (normalize 127.0.0.1 to localhost)
6
11
  if (hostname === "localhost" || hostname === "127.0.0.1") {
7
- return `${url.protocol}//localhost:3308/twig`;
12
+ return `${url.protocol}//localhost:3308/${product}`;
8
13
  }
9
14
 
10
15
  // Docker containers accessing host
11
16
  if (hostname === "host.docker.internal") {
12
- return `${url.protocol}//host.docker.internal:3308/twig`;
17
+ return `${url.protocol}//host.docker.internal:3308/${product}`;
13
18
  }
14
19
 
15
20
  // Production - extract region from hostname, default to US
16
21
  const region = hostname.match(/^(us|eu)\.posthog\.com$/)?.[1] ?? "us";
17
- return `https://gateway.${region}.posthog.com/twig`;
22
+ return `https://gateway.${region}.posthog.com/${product}`;
18
23
  }