@posthog/agent 2.1.131 → 2.1.138
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/adapters/claude/conversion/tool-use-to-acp.d.ts +14 -28
- package/dist/adapters/claude/conversion/tool-use-to-acp.js +118 -165
- package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -1
- package/dist/adapters/claude/permissions/permission-options.js +33 -0
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
- package/dist/adapters/claude/session/jsonl-hydration.d.ts +45 -0
- package/dist/adapters/claude/session/jsonl-hydration.js +444 -0
- package/dist/adapters/claude/session/jsonl-hydration.js.map +1 -0
- package/dist/adapters/claude/tools.js +21 -11
- package/dist/adapters/claude/tools.js.map +1 -1
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +1261 -608
- package/dist/agent.js.map +1 -1
- package/dist/posthog-api.js +6 -2
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.js +1307 -657
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +1285 -637
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +8 -4
- package/src/adapters/base-acp-agent.ts +6 -3
- package/src/adapters/claude/UPSTREAM.md +63 -0
- package/src/adapters/claude/claude-agent.ts +682 -421
- package/src/adapters/claude/conversion/sdk-to-acp.ts +249 -85
- package/src/adapters/claude/conversion/tool-use-to-acp.ts +176 -150
- package/src/adapters/claude/hooks.ts +53 -1
- package/src/adapters/claude/permissions/permission-handlers.ts +39 -21
- package/src/adapters/claude/session/commands.ts +13 -9
- package/src/adapters/claude/session/jsonl-hydration.test.ts +903 -0
- package/src/adapters/claude/session/jsonl-hydration.ts +581 -0
- package/src/adapters/claude/session/mcp-config.ts +2 -5
- package/src/adapters/claude/session/options.ts +58 -6
- package/src/adapters/claude/session/settings.ts +326 -0
- package/src/adapters/claude/tools.ts +1 -0
- package/src/adapters/claude/types.ts +38 -0
- package/src/adapters/codex/spawn.ts +1 -1
- package/src/agent.ts +4 -0
- package/src/execution-mode.ts +26 -10
- package/src/server/agent-server.test.ts +41 -1
- package/src/utils/common.ts +1 -1
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import type { ContentBlock } from "@agentclientprotocol/sdk";
|
|
6
|
+
import type { PostHogAPIClient } from "../../../posthog-api.js";
|
|
7
|
+
import type { StoredEntry } from "../../../types.js";
|
|
8
|
+
|
|
9
|
+
interface ConversationTurn {
|
|
10
|
+
role: "user" | "assistant";
|
|
11
|
+
content: ContentBlock[];
|
|
12
|
+
toolCalls?: ToolCallInfo[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ToolCallInfo {
|
|
16
|
+
toolCallId: string;
|
|
17
|
+
toolName: string;
|
|
18
|
+
input: unknown;
|
|
19
|
+
result?: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface JsonlConfig {
|
|
23
|
+
sessionId: string;
|
|
24
|
+
cwd: string;
|
|
25
|
+
model?: string;
|
|
26
|
+
version?: string;
|
|
27
|
+
gitBranch?: string;
|
|
28
|
+
slug?: string;
|
|
29
|
+
permissionMode?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ClaudeCodeMeta {
|
|
33
|
+
toolCallId?: string;
|
|
34
|
+
toolName?: string;
|
|
35
|
+
toolInput?: unknown;
|
|
36
|
+
toolResponse?: unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface SessionUpdate {
|
|
40
|
+
sessionUpdate: string;
|
|
41
|
+
content?: ContentBlock | ContentBlock[];
|
|
42
|
+
_meta?: { claudeCode?: ClaudeCodeMeta };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const MAX_PROJECT_KEY_LENGTH = 200;
|
|
46
|
+
|
|
47
|
+
function hashString(s: string): string {
|
|
48
|
+
let hash = 0;
|
|
49
|
+
for (let i = 0; i < s.length; i++) {
|
|
50
|
+
hash = (hash << 5) - hash + s.charCodeAt(i);
|
|
51
|
+
hash |= 0;
|
|
52
|
+
}
|
|
53
|
+
return Math.abs(hash).toString(36);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getSessionJsonlPath(sessionId: string, cwd: string): string {
|
|
57
|
+
const configDir =
|
|
58
|
+
process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
|
|
59
|
+
let projectKey = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
60
|
+
if (projectKey.length > MAX_PROJECT_KEY_LENGTH) {
|
|
61
|
+
projectKey = `${projectKey.slice(0, MAX_PROJECT_KEY_LENGTH)}-${hashString(cwd)}`;
|
|
62
|
+
}
|
|
63
|
+
return path.join(configDir, "projects", projectKey, `${sessionId}.jsonl`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function rebuildConversation(
|
|
67
|
+
entries: StoredEntry[],
|
|
68
|
+
): ConversationTurn[] {
|
|
69
|
+
const turns: ConversationTurn[] = [];
|
|
70
|
+
let currentAssistantContent: ContentBlock[] = [];
|
|
71
|
+
let currentToolCalls: ToolCallInfo[] = [];
|
|
72
|
+
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
const method = entry.notification?.method;
|
|
75
|
+
const params = entry.notification?.params as Record<string, unknown>;
|
|
76
|
+
|
|
77
|
+
if (method === "session/update" && params?.update) {
|
|
78
|
+
const update = params.update as SessionUpdate;
|
|
79
|
+
|
|
80
|
+
switch (update.sessionUpdate) {
|
|
81
|
+
case "user_message":
|
|
82
|
+
case "user_message_chunk": {
|
|
83
|
+
if (
|
|
84
|
+
currentAssistantContent.length > 0 ||
|
|
85
|
+
currentToolCalls.length > 0
|
|
86
|
+
) {
|
|
87
|
+
turns.push({
|
|
88
|
+
role: "assistant",
|
|
89
|
+
content: currentAssistantContent,
|
|
90
|
+
toolCalls:
|
|
91
|
+
currentToolCalls.length > 0 ? currentToolCalls : undefined,
|
|
92
|
+
});
|
|
93
|
+
currentAssistantContent = [];
|
|
94
|
+
currentToolCalls = [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const content = update.content;
|
|
98
|
+
const contentArray = Array.isArray(content)
|
|
99
|
+
? content
|
|
100
|
+
: content
|
|
101
|
+
? [content]
|
|
102
|
+
: [];
|
|
103
|
+
|
|
104
|
+
const lastTurn = turns[turns.length - 1];
|
|
105
|
+
if (lastTurn?.role === "user") {
|
|
106
|
+
lastTurn.content.push(...contentArray);
|
|
107
|
+
} else {
|
|
108
|
+
turns.push({ role: "user", content: contentArray });
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
case "agent_message":
|
|
114
|
+
case "agent_message_chunk":
|
|
115
|
+
case "agent_thought_chunk": {
|
|
116
|
+
const content = update.content;
|
|
117
|
+
if (content && !Array.isArray(content)) {
|
|
118
|
+
if (
|
|
119
|
+
content.type === "text" &&
|
|
120
|
+
currentAssistantContent.length > 0 &&
|
|
121
|
+
currentAssistantContent[currentAssistantContent.length - 1]
|
|
122
|
+
.type === "text"
|
|
123
|
+
) {
|
|
124
|
+
const lastBlock = currentAssistantContent[
|
|
125
|
+
currentAssistantContent.length - 1
|
|
126
|
+
] as { type: "text"; text: string };
|
|
127
|
+
lastBlock.text += (
|
|
128
|
+
content as { type: "text"; text: string }
|
|
129
|
+
).text;
|
|
130
|
+
} else {
|
|
131
|
+
currentAssistantContent.push(content);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
case "tool_call":
|
|
138
|
+
case "tool_call_update": {
|
|
139
|
+
const meta = update._meta?.claudeCode;
|
|
140
|
+
if (meta) {
|
|
141
|
+
const { toolCallId, toolName, toolInput, toolResponse } = meta;
|
|
142
|
+
|
|
143
|
+
if (toolCallId && toolName) {
|
|
144
|
+
let toolCall = currentToolCalls.find(
|
|
145
|
+
(tc) => tc.toolCallId === toolCallId,
|
|
146
|
+
);
|
|
147
|
+
if (!toolCall) {
|
|
148
|
+
toolCall = { toolCallId, toolName, input: toolInput };
|
|
149
|
+
currentToolCalls.push(toolCall);
|
|
150
|
+
}
|
|
151
|
+
if (toolResponse !== undefined) {
|
|
152
|
+
toolCall.result = toolResponse;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
case "tool_result": {
|
|
160
|
+
const meta = update._meta?.claudeCode;
|
|
161
|
+
if (meta) {
|
|
162
|
+
const { toolCallId, toolResponse } = meta;
|
|
163
|
+
if (toolCallId) {
|
|
164
|
+
const toolCall = currentToolCalls.find(
|
|
165
|
+
(tc) => tc.toolCallId === toolCallId,
|
|
166
|
+
);
|
|
167
|
+
if (toolCall && toolResponse !== undefined) {
|
|
168
|
+
toolCall.result = toolResponse;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (currentAssistantContent.length > 0 || currentToolCalls.length > 0) {
|
|
179
|
+
turns.push({
|
|
180
|
+
role: "assistant",
|
|
181
|
+
content: currentAssistantContent,
|
|
182
|
+
toolCalls: currentToolCalls.length > 0 ? currentToolCalls : undefined,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return turns;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const CHARS_PER_TOKEN = 4;
|
|
190
|
+
const DEFAULT_MAX_TOKENS = 150_000;
|
|
191
|
+
|
|
192
|
+
function estimateTurnTokens(turn: ConversationTurn): number {
|
|
193
|
+
let chars = 0;
|
|
194
|
+
for (const block of turn.content) {
|
|
195
|
+
if ("text" in block && typeof block.text === "string") {
|
|
196
|
+
chars += block.text.length;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (turn.toolCalls) {
|
|
200
|
+
for (const tc of turn.toolCalls) {
|
|
201
|
+
chars += JSON.stringify(tc.input ?? "").length;
|
|
202
|
+
if (tc.result !== undefined) {
|
|
203
|
+
chars +=
|
|
204
|
+
typeof tc.result === "string"
|
|
205
|
+
? tc.result.length
|
|
206
|
+
: JSON.stringify(tc.result).length;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return Math.ceil(chars / CHARS_PER_TOKEN);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function selectRecentTurns(
|
|
214
|
+
turns: ConversationTurn[],
|
|
215
|
+
maxTokens = DEFAULT_MAX_TOKENS,
|
|
216
|
+
): ConversationTurn[] {
|
|
217
|
+
let budget = maxTokens;
|
|
218
|
+
let startIndex = turns.length;
|
|
219
|
+
|
|
220
|
+
for (let i = turns.length - 1; i >= 0; i--) {
|
|
221
|
+
const cost = estimateTurnTokens(turns[i]);
|
|
222
|
+
if (cost > budget) break;
|
|
223
|
+
budget -= cost;
|
|
224
|
+
startIndex = i;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Ensure we start on a user turn so the conversation is well-formed
|
|
228
|
+
while (startIndex < turns.length && turns[startIndex].role !== "user") {
|
|
229
|
+
startIndex++;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return turns.slice(startIndex);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const BASE62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
236
|
+
|
|
237
|
+
function generateMessageId(): string {
|
|
238
|
+
const bytes = new Uint8Array(24);
|
|
239
|
+
crypto.getRandomValues(bytes);
|
|
240
|
+
let id = "msg_01";
|
|
241
|
+
for (const b of bytes) {
|
|
242
|
+
id += BASE62[b % 62];
|
|
243
|
+
}
|
|
244
|
+
return id;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const ADJECTIVES = [
|
|
248
|
+
"bright",
|
|
249
|
+
"calm",
|
|
250
|
+
"daring",
|
|
251
|
+
"eager",
|
|
252
|
+
"fair",
|
|
253
|
+
"gentle",
|
|
254
|
+
"happy",
|
|
255
|
+
"keen",
|
|
256
|
+
"lively",
|
|
257
|
+
"merry",
|
|
258
|
+
"noble",
|
|
259
|
+
"polite",
|
|
260
|
+
"quick",
|
|
261
|
+
"sharp",
|
|
262
|
+
"warm",
|
|
263
|
+
"witty",
|
|
264
|
+
];
|
|
265
|
+
const VERBS = [
|
|
266
|
+
"blazing",
|
|
267
|
+
"crafting",
|
|
268
|
+
"dashing",
|
|
269
|
+
"flowing",
|
|
270
|
+
"gliding",
|
|
271
|
+
"humming",
|
|
272
|
+
"jumping",
|
|
273
|
+
"linking",
|
|
274
|
+
"melting",
|
|
275
|
+
"nesting",
|
|
276
|
+
"pacing",
|
|
277
|
+
"roaming",
|
|
278
|
+
"sailing",
|
|
279
|
+
"turning",
|
|
280
|
+
"waving",
|
|
281
|
+
"zoning",
|
|
282
|
+
];
|
|
283
|
+
const NOUNS = [
|
|
284
|
+
"aurora",
|
|
285
|
+
"breeze",
|
|
286
|
+
"cedar",
|
|
287
|
+
"delta",
|
|
288
|
+
"ember",
|
|
289
|
+
"frost",
|
|
290
|
+
"grove",
|
|
291
|
+
"haven",
|
|
292
|
+
"inlet",
|
|
293
|
+
"jewel",
|
|
294
|
+
"knoll",
|
|
295
|
+
"lotus",
|
|
296
|
+
"maple",
|
|
297
|
+
"nexus",
|
|
298
|
+
"oasis",
|
|
299
|
+
"prism",
|
|
300
|
+
];
|
|
301
|
+
|
|
302
|
+
function generateSlug(): string {
|
|
303
|
+
const pick = (arr: string[]) => arr[Math.floor(Math.random() * arr.length)];
|
|
304
|
+
return `${pick(ADJECTIVES)}-${pick(VERBS)}-${pick(NOUNS)}`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function conversationTurnsToJsonlEntries(
|
|
308
|
+
turns: ConversationTurn[],
|
|
309
|
+
config: JsonlConfig,
|
|
310
|
+
): string[] {
|
|
311
|
+
const lines: string[] = [];
|
|
312
|
+
let parentUuid: string | null = null;
|
|
313
|
+
const model = config.model ?? "claude-opus-4-6";
|
|
314
|
+
const version = config.version ?? "2.1.63";
|
|
315
|
+
const gitBranch = config.gitBranch ?? "";
|
|
316
|
+
const slug = config.slug ?? generateSlug();
|
|
317
|
+
const permissionMode = config.permissionMode ?? "default";
|
|
318
|
+
const baseTime = Date.now() - turns.length * 3000;
|
|
319
|
+
let turnIndex = 0;
|
|
320
|
+
|
|
321
|
+
for (const turn of turns) {
|
|
322
|
+
const timestamp = new Date(baseTime + turnIndex * 3000).toISOString();
|
|
323
|
+
turnIndex++;
|
|
324
|
+
if (turn.role === "user") {
|
|
325
|
+
lines.push(
|
|
326
|
+
JSON.stringify({
|
|
327
|
+
type: "queue-operation",
|
|
328
|
+
operation: "enqueue",
|
|
329
|
+
timestamp,
|
|
330
|
+
sessionId: config.sessionId,
|
|
331
|
+
}),
|
|
332
|
+
);
|
|
333
|
+
lines.push(
|
|
334
|
+
JSON.stringify({
|
|
335
|
+
type: "queue-operation",
|
|
336
|
+
operation: "dequeue",
|
|
337
|
+
timestamp,
|
|
338
|
+
sessionId: config.sessionId,
|
|
339
|
+
}),
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
const uuid = randomUUID();
|
|
343
|
+
const textParts = turn.content
|
|
344
|
+
.filter(
|
|
345
|
+
(block) =>
|
|
346
|
+
"text" in block && typeof block.text === "string" && block.text,
|
|
347
|
+
)
|
|
348
|
+
.map((block) => (block as { text: string }).text);
|
|
349
|
+
|
|
350
|
+
const userText = textParts.length > 0 ? textParts.join("") : " ";
|
|
351
|
+
|
|
352
|
+
lines.push(
|
|
353
|
+
JSON.stringify({
|
|
354
|
+
parentUuid,
|
|
355
|
+
isSidechain: false,
|
|
356
|
+
userType: "external",
|
|
357
|
+
cwd: config.cwd,
|
|
358
|
+
sessionId: config.sessionId,
|
|
359
|
+
version,
|
|
360
|
+
gitBranch,
|
|
361
|
+
slug,
|
|
362
|
+
type: "user",
|
|
363
|
+
message: {
|
|
364
|
+
role: "user",
|
|
365
|
+
content: [{ type: "text", text: userText }],
|
|
366
|
+
},
|
|
367
|
+
uuid,
|
|
368
|
+
timestamp,
|
|
369
|
+
permissionMode,
|
|
370
|
+
}),
|
|
371
|
+
);
|
|
372
|
+
parentUuid = uuid;
|
|
373
|
+
} else {
|
|
374
|
+
const allBlocks: unknown[] = [];
|
|
375
|
+
|
|
376
|
+
for (const block of turn.content) {
|
|
377
|
+
const blockType = (block as { type: string }).type;
|
|
378
|
+
if (blockType === "thinking" || blockType === "text") {
|
|
379
|
+
allBlocks.push(block);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (turn.toolCalls) {
|
|
384
|
+
for (const tc of turn.toolCalls) {
|
|
385
|
+
allBlocks.push({
|
|
386
|
+
type: "tool_use",
|
|
387
|
+
id: tc.toolCallId,
|
|
388
|
+
name: tc.toolName,
|
|
389
|
+
input: tc.input,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const msgId = generateMessageId();
|
|
395
|
+
const hasToolUse = allBlocks.some(
|
|
396
|
+
(b) => (b as { type: string }).type === "tool_use",
|
|
397
|
+
);
|
|
398
|
+
const lastStopReason = hasToolUse ? "tool_use" : "end_turn";
|
|
399
|
+
|
|
400
|
+
for (let i = 0; i < allBlocks.length; i++) {
|
|
401
|
+
const block = allBlocks[i];
|
|
402
|
+
const isLast = i === allBlocks.length - 1;
|
|
403
|
+
const uuid = randomUUID();
|
|
404
|
+
|
|
405
|
+
lines.push(
|
|
406
|
+
JSON.stringify({
|
|
407
|
+
parentUuid,
|
|
408
|
+
isSidechain: false,
|
|
409
|
+
userType: "external",
|
|
410
|
+
cwd: config.cwd,
|
|
411
|
+
sessionId: config.sessionId,
|
|
412
|
+
version,
|
|
413
|
+
gitBranch,
|
|
414
|
+
slug,
|
|
415
|
+
type: "assistant",
|
|
416
|
+
message: {
|
|
417
|
+
model,
|
|
418
|
+
id: msgId,
|
|
419
|
+
type: "message",
|
|
420
|
+
role: "assistant",
|
|
421
|
+
content: [block],
|
|
422
|
+
stop_reason: isLast ? lastStopReason : null,
|
|
423
|
+
stop_sequence: null,
|
|
424
|
+
usage: {
|
|
425
|
+
input_tokens: 0,
|
|
426
|
+
cache_creation_input_tokens: 0,
|
|
427
|
+
cache_read_input_tokens: 0,
|
|
428
|
+
output_tokens: 0,
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
uuid,
|
|
432
|
+
timestamp,
|
|
433
|
+
}),
|
|
434
|
+
);
|
|
435
|
+
parentUuid = uuid;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (turn.toolCalls) {
|
|
439
|
+
for (const tc of turn.toolCalls) {
|
|
440
|
+
if (tc.result === undefined) continue;
|
|
441
|
+
|
|
442
|
+
const uuid = randomUUID();
|
|
443
|
+
const resultText =
|
|
444
|
+
typeof tc.result === "string"
|
|
445
|
+
? tc.result
|
|
446
|
+
: JSON.stringify(tc.result);
|
|
447
|
+
|
|
448
|
+
lines.push(
|
|
449
|
+
JSON.stringify({
|
|
450
|
+
parentUuid,
|
|
451
|
+
isSidechain: false,
|
|
452
|
+
userType: "external",
|
|
453
|
+
cwd: config.cwd,
|
|
454
|
+
sessionId: config.sessionId,
|
|
455
|
+
version,
|
|
456
|
+
gitBranch,
|
|
457
|
+
slug,
|
|
458
|
+
type: "user",
|
|
459
|
+
message: {
|
|
460
|
+
role: "user",
|
|
461
|
+
content: [
|
|
462
|
+
{
|
|
463
|
+
type: "tool_result",
|
|
464
|
+
tool_use_id: tc.toolCallId,
|
|
465
|
+
content: resultText,
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
},
|
|
469
|
+
uuid,
|
|
470
|
+
timestamp,
|
|
471
|
+
}),
|
|
472
|
+
);
|
|
473
|
+
parentUuid = uuid;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return lines;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
interface HydrationLog {
|
|
483
|
+
info: (msg: string, data?: unknown) => void;
|
|
484
|
+
warn: (msg: string, data?: unknown) => void;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export async function hydrateSessionJsonl(params: {
|
|
488
|
+
sessionId: string;
|
|
489
|
+
cwd: string;
|
|
490
|
+
taskId: string;
|
|
491
|
+
runId: string;
|
|
492
|
+
model?: string;
|
|
493
|
+
gitBranch?: string;
|
|
494
|
+
permissionMode?: string;
|
|
495
|
+
posthogAPI: PostHogAPIClient;
|
|
496
|
+
log: HydrationLog;
|
|
497
|
+
}): Promise<void> {
|
|
498
|
+
const { posthogAPI, log } = params;
|
|
499
|
+
|
|
500
|
+
try {
|
|
501
|
+
const jsonlPath = getSessionJsonlPath(params.sessionId, params.cwd);
|
|
502
|
+
try {
|
|
503
|
+
await fs.access(jsonlPath);
|
|
504
|
+
log.info("Local JSONL exists, skipping S3 hydration", {
|
|
505
|
+
sessionId: params.sessionId,
|
|
506
|
+
});
|
|
507
|
+
return;
|
|
508
|
+
} catch {
|
|
509
|
+
// File doesn't exist, proceed with hydration
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const taskRun = await posthogAPI.getTaskRun(params.taskId, params.runId);
|
|
513
|
+
if (!taskRun.log_url) {
|
|
514
|
+
log.info("No log URL, skipping JSONL hydration");
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const entries = await posthogAPI.fetchTaskRunLogs(taskRun);
|
|
519
|
+
if (entries.length === 0) {
|
|
520
|
+
log.info("No S3 log entries, skipping JSONL hydration");
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const entryCounts: Record<string, number> = {};
|
|
525
|
+
for (const entry of entries) {
|
|
526
|
+
const method = entry.notification?.method ?? "unknown";
|
|
527
|
+
const entryParams = entry.notification?.params as
|
|
528
|
+
| Record<string, unknown>
|
|
529
|
+
| undefined;
|
|
530
|
+
const update = entryParams?.update as
|
|
531
|
+
| { sessionUpdate?: string }
|
|
532
|
+
| undefined;
|
|
533
|
+
const key = update?.sessionUpdate
|
|
534
|
+
? `${method}:${update.sessionUpdate}`
|
|
535
|
+
: method;
|
|
536
|
+
entryCounts[key] = (entryCounts[key] ?? 0) + 1;
|
|
537
|
+
}
|
|
538
|
+
log.info("S3 log entry breakdown", {
|
|
539
|
+
totalEntries: entries.length,
|
|
540
|
+
types: entryCounts,
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
const allTurns = rebuildConversation(entries);
|
|
544
|
+
if (allTurns.length === 0) {
|
|
545
|
+
log.info("No conversation in S3 logs, skipping JSONL hydration");
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const conversation = selectRecentTurns(allTurns);
|
|
550
|
+
log.info("Selected recent turns for hydration", {
|
|
551
|
+
totalTurns: allTurns.length,
|
|
552
|
+
selectedTurns: conversation.length,
|
|
553
|
+
turnRoles: conversation.map((t) => t.role),
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
const jsonlLines = conversationTurnsToJsonlEntries(conversation, {
|
|
557
|
+
sessionId: params.sessionId,
|
|
558
|
+
cwd: params.cwd,
|
|
559
|
+
model: params.model,
|
|
560
|
+
gitBranch: params.gitBranch,
|
|
561
|
+
permissionMode: params.permissionMode,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
await fs.mkdir(path.dirname(jsonlPath), { recursive: true });
|
|
565
|
+
|
|
566
|
+
const tmpPath = `${jsonlPath}.tmp.${Date.now()}`;
|
|
567
|
+
await fs.writeFile(tmpPath, `${jsonlLines.join("\n")}\n`);
|
|
568
|
+
await fs.rename(tmpPath, jsonlPath);
|
|
569
|
+
|
|
570
|
+
log.info("Hydrated session JSONL from S3", {
|
|
571
|
+
sessionId: params.sessionId,
|
|
572
|
+
turns: conversation.length,
|
|
573
|
+
lines: jsonlLines.length,
|
|
574
|
+
});
|
|
575
|
+
} catch (err) {
|
|
576
|
+
log.warn("Failed to hydrate session JSONL, continuing", {
|
|
577
|
+
sessionId: params.sessionId,
|
|
578
|
+
error: err instanceof Error ? err.message : String(err),
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
LoadSessionRequest,
|
|
3
|
-
NewSessionRequest,
|
|
4
|
-
} from "@agentclientprotocol/sdk";
|
|
1
|
+
import type { NewSessionRequest } from "@agentclientprotocol/sdk";
|
|
5
2
|
import type { McpServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
|
6
3
|
|
|
7
4
|
export function parseMcpServers(
|
|
8
|
-
params: NewSessionRequest
|
|
5
|
+
params: Pick<NewSessionRequest, "mcpServers">,
|
|
9
6
|
): Record<string, McpServerConfig> {
|
|
10
7
|
const mcpServers: Record<string, McpServerConfig> = {};
|
|
11
8
|
if (!Array.isArray(params.mcpServers)) {
|