@posthog/agent 2.3.43 → 2.3.53
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/dist/agent.js +19 -2
- package/dist/agent.js.map +1 -1
- package/dist/posthog-api.js +1 -1
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.d.ts +4 -0
- package/dist/server/agent-server.js +108 -5
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +108 -5
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude/claude-agent.ts +5 -0
- package/src/adapters/claude/types.ts +4 -0
- package/src/server/agent-server.ts +119 -4
- package/src/server/question-relay.test.ts +17 -3
- package/src/session-log-writer.ts +17 -1
package/package.json
CHANGED
|
@@ -346,6 +346,11 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
346
346
|
? Math.min(...contextWindows)
|
|
347
347
|
: getDefaultContextWindow(this.session.modelId ?? "");
|
|
348
348
|
|
|
349
|
+
this.session.contextSize = contextWindowSize;
|
|
350
|
+
if (lastAssistantTotalUsage !== null) {
|
|
351
|
+
this.session.contextUsed = lastAssistantTotalUsage;
|
|
352
|
+
}
|
|
353
|
+
|
|
349
354
|
// Send usage_update notification
|
|
350
355
|
if (lastAssistantTotalUsage !== null) {
|
|
351
356
|
await this.client.sessionUpdate({
|
|
@@ -53,6 +53,10 @@ export type Session = BaseSession & {
|
|
|
53
53
|
effort?: EffortLevel;
|
|
54
54
|
configOptions: SessionConfigOption[];
|
|
55
55
|
accumulatedUsage: AccumulatedUsage;
|
|
56
|
+
/** Latest context window usage (total tokens from last assistant message) */
|
|
57
|
+
contextUsed?: number;
|
|
58
|
+
/** Context window size in tokens */
|
|
59
|
+
contextSize?: number;
|
|
56
60
|
promptRunning: boolean;
|
|
57
61
|
pendingMessages: Map<string, PendingMessage>;
|
|
58
62
|
nextPendingOrder: number;
|
|
@@ -162,6 +162,12 @@ export class AgentServer {
|
|
|
162
162
|
private questionRelayedToSlack = false;
|
|
163
163
|
private detectedPrUrl: string | null = null;
|
|
164
164
|
private resumeState: ResumeState | null = null;
|
|
165
|
+
// Guards against concurrent session initialization. autoInitializeSession() and
|
|
166
|
+
// the GET /events SSE handler can both call initializeSession() — the SSE connection
|
|
167
|
+
// often arrives while newSession() is still awaited (this.session is still null),
|
|
168
|
+
// causing a second session to be created and duplicate Slack messages to be sent.
|
|
169
|
+
private initializationPromise: Promise<void> | null = null;
|
|
170
|
+
private pendingEvents: Record<string, unknown>[] = [];
|
|
165
171
|
|
|
166
172
|
private emitConsoleLog = (
|
|
167
173
|
level: LogLevel,
|
|
@@ -264,6 +270,7 @@ export class AgentServer {
|
|
|
264
270
|
await this.initializeSession(payload, sseController);
|
|
265
271
|
} else {
|
|
266
272
|
this.session.sseController = sseController;
|
|
273
|
+
this.replayPendingEvents();
|
|
267
274
|
}
|
|
268
275
|
|
|
269
276
|
this.sendSseEvent(sseController, {
|
|
@@ -483,6 +490,8 @@ export class AgentServer {
|
|
|
483
490
|
`Processing user message (detectedPrUrl=${this.detectedPrUrl ?? "none"}): ${content.substring(0, 100)}...`,
|
|
484
491
|
);
|
|
485
492
|
|
|
493
|
+
this.session.logWriter.resetTurnMessages(this.session.payload.run_id);
|
|
494
|
+
|
|
486
495
|
const result = await this.session.clientConnection.prompt({
|
|
487
496
|
sessionId: this.session.acpSessionId,
|
|
488
497
|
prompt: [{ type: "text", text: content }],
|
|
@@ -501,7 +510,31 @@ export class AgentServer {
|
|
|
501
510
|
|
|
502
511
|
this.broadcastTurnComplete(result.stopReason);
|
|
503
512
|
|
|
504
|
-
|
|
513
|
+
if (result.stopReason === "end_turn") {
|
|
514
|
+
// Relay the response to Slack. For follow-ups this is the primary
|
|
515
|
+
// delivery path — the HTTP caller only handles reactions.
|
|
516
|
+
this.relayAgentResponse(this.session.payload).catch((err) =>
|
|
517
|
+
this.logger.warn("Failed to relay follow-up response", err),
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Flush logs and include the assistant's response text so callers
|
|
522
|
+
// (e.g. Slack follow-up forwarding) can extract it without racing
|
|
523
|
+
// against async log persistence to object storage.
|
|
524
|
+
let assistantMessage: string | undefined;
|
|
525
|
+
try {
|
|
526
|
+
await this.session.logWriter.flush(this.session.payload.run_id);
|
|
527
|
+
assistantMessage = this.session.logWriter.getFullAgentResponse(
|
|
528
|
+
this.session.payload.run_id,
|
|
529
|
+
);
|
|
530
|
+
} catch {
|
|
531
|
+
this.logger.warn("Failed to extract assistant message from logs");
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
stopReason: result.stopReason,
|
|
536
|
+
...(assistantMessage && { assistant_message: assistantMessage }),
|
|
537
|
+
};
|
|
505
538
|
}
|
|
506
539
|
|
|
507
540
|
case POSTHOG_NOTIFICATIONS.CANCEL:
|
|
@@ -530,6 +563,40 @@ export class AgentServer {
|
|
|
530
563
|
private async initializeSession(
|
|
531
564
|
payload: JwtPayload,
|
|
532
565
|
sseController: SseController | null,
|
|
566
|
+
): Promise<void> {
|
|
567
|
+
// Race condition guard: autoInitializeSession() starts first, but while it awaits
|
|
568
|
+
// newSession() (which takes ~1-2s for MCP metadata fetch), the Temporal relay connects
|
|
569
|
+
// to GET /events. That handler sees this.session === null and calls initializeSession()
|
|
570
|
+
// again, creating a duplicate session that sends the same prompt twice — resulting in
|
|
571
|
+
// duplicate Slack messages. This lock ensures the second caller waits for the first
|
|
572
|
+
// initialization to finish and reuses the session.
|
|
573
|
+
if (this.initializationPromise) {
|
|
574
|
+
this.logger.info("Waiting for in-progress initialization", {
|
|
575
|
+
runId: payload.run_id,
|
|
576
|
+
});
|
|
577
|
+
await this.initializationPromise;
|
|
578
|
+
// After waiting, just attach the SSE controller if needed
|
|
579
|
+
if (this.session && sseController) {
|
|
580
|
+
this.session.sseController = sseController;
|
|
581
|
+
this.replayPendingEvents();
|
|
582
|
+
}
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
this.initializationPromise = this._doInitializeSession(
|
|
587
|
+
payload,
|
|
588
|
+
sseController,
|
|
589
|
+
);
|
|
590
|
+
try {
|
|
591
|
+
await this.initializationPromise;
|
|
592
|
+
} finally {
|
|
593
|
+
this.initializationPromise = null;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
private async _doInitializeSession(
|
|
598
|
+
payload: JwtPayload,
|
|
599
|
+
sseController: SseController | null,
|
|
533
600
|
): Promise<void> {
|
|
534
601
|
if (this.session) {
|
|
535
602
|
await this.cleanupSession();
|
|
@@ -770,6 +837,8 @@ export class AgentServer {
|
|
|
770
837
|
usedInitialPromptOverride: !!initialPromptOverride,
|
|
771
838
|
});
|
|
772
839
|
|
|
840
|
+
this.session.logWriter.resetTurnMessages(payload.run_id);
|
|
841
|
+
|
|
773
842
|
const result = await this.session.clientConnection.prompt({
|
|
774
843
|
sessionId: this.session.acpSessionId,
|
|
775
844
|
prompt: [{ type: "text", text: initialPrompt }],
|
|
@@ -809,8 +878,8 @@ export class AgentServer {
|
|
|
809
878
|
const pendingUserMessage = this.getPendingUserMessage(taskRun);
|
|
810
879
|
|
|
811
880
|
const sandboxContext = this.resumeState.snapshotApplied
|
|
812
|
-
? `The
|
|
813
|
-
: `The
|
|
881
|
+
? `The workspace environment (all files, packages, and code changes) has been fully restored from where you left off.`
|
|
882
|
+
: `The workspace files from the previous session were not restored (the file snapshot may have expired), so you are starting with a fresh environment. Your conversation history is fully preserved below.`;
|
|
814
883
|
|
|
815
884
|
let resumePrompt: string;
|
|
816
885
|
if (pendingUserMessage) {
|
|
@@ -842,6 +911,8 @@ export class AgentServer {
|
|
|
842
911
|
// Clear resume state so it's not reused
|
|
843
912
|
this.resumeState = null;
|
|
844
913
|
|
|
914
|
+
this.session.logWriter.resetTurnMessages(payload.run_id);
|
|
915
|
+
|
|
845
916
|
const result = await this.session.clientConnection.prompt({
|
|
846
917
|
sessionId: this.session.acpSessionId,
|
|
847
918
|
prompt: [{ type: "text", text: resumePrompt }],
|
|
@@ -852,6 +923,10 @@ export class AgentServer {
|
|
|
852
923
|
});
|
|
853
924
|
|
|
854
925
|
this.broadcastTurnComplete(result.stopReason);
|
|
926
|
+
|
|
927
|
+
if (result.stopReason === "end_turn") {
|
|
928
|
+
await this.relayAgentResponse(payload);
|
|
929
|
+
}
|
|
855
930
|
} catch (error) {
|
|
856
931
|
this.logger.error("Failed to send resume message", error);
|
|
857
932
|
if (this.session) {
|
|
@@ -992,6 +1067,27 @@ Important:
|
|
|
992
1067
|
`;
|
|
993
1068
|
}
|
|
994
1069
|
|
|
1070
|
+
if (!this.config.repositoryPath) {
|
|
1071
|
+
return `
|
|
1072
|
+
# Cloud Task Execution — No Repository Mode
|
|
1073
|
+
|
|
1074
|
+
You are a helpful assistant with access to PostHog via MCP tools. You can help with both code tasks and data/analytics questions.
|
|
1075
|
+
|
|
1076
|
+
When the user asks about analytics, data, metrics, events, funnels, dashboards, feature flags, experiments, or anything PostHog-related:
|
|
1077
|
+
- Use your PostHog MCP tools to query data, search insights, and provide real answers
|
|
1078
|
+
- Do NOT tell the user to check an external analytics platform — you ARE the analytics platform
|
|
1079
|
+
- Use tools like insight-query, query-run, event-definitions-list, and others to answer questions directly
|
|
1080
|
+
|
|
1081
|
+
When the user asks for code changes or software engineering tasks:
|
|
1082
|
+
- Let them know you can help but don't have a repository connected for this session
|
|
1083
|
+
- Offer to write code snippets, scripts, or provide guidance
|
|
1084
|
+
|
|
1085
|
+
Important:
|
|
1086
|
+
- Do NOT create branches, commits, or pull requests in this mode.
|
|
1087
|
+
- Prefer using MCP tools to answer questions with real data over giving generic advice.
|
|
1088
|
+
`;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
995
1091
|
return `
|
|
996
1092
|
# Cloud Task Execution
|
|
997
1093
|
|
|
@@ -1124,6 +1220,12 @@ Important:
|
|
|
1124
1220
|
},
|
|
1125
1221
|
};
|
|
1126
1222
|
},
|
|
1223
|
+
extNotification: async (
|
|
1224
|
+
method: string,
|
|
1225
|
+
params: Record<string, unknown>,
|
|
1226
|
+
) => {
|
|
1227
|
+
this.logger.debug("Extension notification", { method, params });
|
|
1228
|
+
},
|
|
1127
1229
|
sessionUpdate: async (params: {
|
|
1128
1230
|
sessionId: string;
|
|
1129
1231
|
update?: Record<string, unknown>;
|
|
@@ -1176,7 +1278,7 @@ Important:
|
|
|
1176
1278
|
});
|
|
1177
1279
|
}
|
|
1178
1280
|
|
|
1179
|
-
const message = this.session.logWriter.
|
|
1281
|
+
const message = this.session.logWriter.getFullAgentResponse(payload.run_id);
|
|
1180
1282
|
if (!message) {
|
|
1181
1283
|
this.logger.warn("No agent message found for Slack relay", {
|
|
1182
1284
|
taskId: payload.task_id,
|
|
@@ -1385,6 +1487,7 @@ Important:
|
|
|
1385
1487
|
this.session.sseController.close();
|
|
1386
1488
|
}
|
|
1387
1489
|
|
|
1490
|
+
this.pendingEvents = [];
|
|
1388
1491
|
this.session = null;
|
|
1389
1492
|
}
|
|
1390
1493
|
|
|
@@ -1444,6 +1547,18 @@ Important:
|
|
|
1444
1547
|
private broadcastEvent(event: Record<string, unknown>): void {
|
|
1445
1548
|
if (this.session?.sseController) {
|
|
1446
1549
|
this.sendSseEvent(this.session.sseController, event);
|
|
1550
|
+
} else if (this.session) {
|
|
1551
|
+
// Buffer events during initialization (sseController not yet attached)
|
|
1552
|
+
this.pendingEvents.push(event);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
private replayPendingEvents(): void {
|
|
1557
|
+
if (!this.session?.sseController || this.pendingEvents.length === 0) return;
|
|
1558
|
+
const events = this.pendingEvents;
|
|
1559
|
+
this.pendingEvents = [];
|
|
1560
|
+
for (const event of events) {
|
|
1561
|
+
this.sendSseEvent(this.session.sseController, event);
|
|
1447
1562
|
}
|
|
1448
1563
|
}
|
|
1449
1564
|
|
|
@@ -248,7 +248,7 @@ describe("Question relay", () => {
|
|
|
248
248
|
payload: TEST_PAYLOAD,
|
|
249
249
|
logWriter: {
|
|
250
250
|
flush: vi.fn().mockResolvedValue(undefined),
|
|
251
|
-
|
|
251
|
+
getFullAgentResponse: vi.fn().mockReturnValue("agent response"),
|
|
252
252
|
isRegistered: vi.fn().mockReturnValue(true),
|
|
253
253
|
},
|
|
254
254
|
};
|
|
@@ -269,7 +269,7 @@ describe("Question relay", () => {
|
|
|
269
269
|
payload: TEST_PAYLOAD,
|
|
270
270
|
logWriter: {
|
|
271
271
|
flush: vi.fn().mockResolvedValue(undefined),
|
|
272
|
-
|
|
272
|
+
getFullAgentResponse: vi.fn().mockReturnValue("agent response"),
|
|
273
273
|
isRegistered: vi.fn().mockReturnValue(true),
|
|
274
274
|
},
|
|
275
275
|
};
|
|
@@ -293,7 +293,7 @@ describe("Question relay", () => {
|
|
|
293
293
|
payload: TEST_PAYLOAD,
|
|
294
294
|
logWriter: {
|
|
295
295
|
flush: vi.fn().mockResolvedValue(undefined),
|
|
296
|
-
|
|
296
|
+
getFullAgentResponse: vi.fn().mockReturnValue(null),
|
|
297
297
|
isRegistered: vi.fn().mockReturnValue(true),
|
|
298
298
|
},
|
|
299
299
|
};
|
|
@@ -323,6 +323,13 @@ describe("Question relay", () => {
|
|
|
323
323
|
payload: TEST_PAYLOAD,
|
|
324
324
|
acpSessionId: "acp-session",
|
|
325
325
|
clientConnection: { prompt: promptSpy },
|
|
326
|
+
logWriter: {
|
|
327
|
+
flushAll: vi.fn().mockResolvedValue(undefined),
|
|
328
|
+
getFullAgentResponse: vi.fn().mockReturnValue(null),
|
|
329
|
+
resetTurnMessages: vi.fn(),
|
|
330
|
+
flush: vi.fn().mockResolvedValue(undefined),
|
|
331
|
+
isRegistered: vi.fn().mockReturnValue(true),
|
|
332
|
+
},
|
|
326
333
|
};
|
|
327
334
|
|
|
328
335
|
await server.sendInitialTaskMessage(TEST_PAYLOAD);
|
|
@@ -350,6 +357,13 @@ describe("Question relay", () => {
|
|
|
350
357
|
payload: TEST_PAYLOAD,
|
|
351
358
|
acpSessionId: "acp-session",
|
|
352
359
|
clientConnection: { prompt: promptSpy },
|
|
360
|
+
logWriter: {
|
|
361
|
+
flushAll: vi.fn().mockResolvedValue(undefined),
|
|
362
|
+
getFullAgentResponse: vi.fn().mockReturnValue(null),
|
|
363
|
+
resetTurnMessages: vi.fn(),
|
|
364
|
+
flush: vi.fn().mockResolvedValue(undefined),
|
|
365
|
+
isRegistered: vi.fn().mockReturnValue(true),
|
|
366
|
+
},
|
|
353
367
|
};
|
|
354
368
|
|
|
355
369
|
await server.sendInitialTaskMessage(TEST_PAYLOAD);
|
|
@@ -24,6 +24,7 @@ interface SessionState {
|
|
|
24
24
|
context: SessionContext;
|
|
25
25
|
chunkBuffer?: ChunkBuffer;
|
|
26
26
|
lastAgentMessage?: string;
|
|
27
|
+
currentTurnMessages: string[];
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export class SessionLogWriter {
|
|
@@ -69,7 +70,7 @@ export class SessionLogWriter {
|
|
|
69
70
|
taskId: context.taskId,
|
|
70
71
|
runId: context.runId,
|
|
71
72
|
});
|
|
72
|
-
this.sessions.set(sessionId, { context });
|
|
73
|
+
this.sessions.set(sessionId, { context, currentTurnMessages: [] });
|
|
73
74
|
|
|
74
75
|
this.lastFlushAttemptTime.set(sessionId, Date.now());
|
|
75
76
|
|
|
@@ -127,6 +128,7 @@ export class SessionLogWriter {
|
|
|
127
128
|
const nonChunkAgentText = this.extractAgentMessageText(message);
|
|
128
129
|
if (nonChunkAgentText) {
|
|
129
130
|
session.lastAgentMessage = nonChunkAgentText;
|
|
131
|
+
session.currentTurnMessages.push(nonChunkAgentText);
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
const entry: StoredNotification = {
|
|
@@ -240,6 +242,7 @@ export class SessionLogWriter {
|
|
|
240
242
|
const { text, firstTimestamp } = session.chunkBuffer;
|
|
241
243
|
session.chunkBuffer = undefined;
|
|
242
244
|
session.lastAgentMessage = text;
|
|
245
|
+
session.currentTurnMessages.push(text);
|
|
243
246
|
|
|
244
247
|
const entry: StoredNotification = {
|
|
245
248
|
type: "notification",
|
|
@@ -270,6 +273,19 @@ export class SessionLogWriter {
|
|
|
270
273
|
return this.sessions.get(sessionId)?.lastAgentMessage;
|
|
271
274
|
}
|
|
272
275
|
|
|
276
|
+
getFullAgentResponse(sessionId: string): string | undefined {
|
|
277
|
+
const session = this.sessions.get(sessionId);
|
|
278
|
+
if (!session || session.currentTurnMessages.length === 0) return undefined;
|
|
279
|
+
return session.currentTurnMessages.join("\n\n");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
resetTurnMessages(sessionId: string): void {
|
|
283
|
+
const session = this.sessions.get(sessionId);
|
|
284
|
+
if (session) {
|
|
285
|
+
session.currentTurnMessages = [];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
273
289
|
private extractAgentMessageText(
|
|
274
290
|
message: Record<string, unknown>,
|
|
275
291
|
): string | null {
|