@posthog/agent 2.3.526 → 2.3.535
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 +30 -5
- package/dist/agent.js.map +1 -1
- package/dist/posthog-api.js +5 -1
- package/dist/posthog-api.js.map +1 -1
- package/dist/pr-url-detector.d.ts +12 -0
- package/dist/pr-url-detector.js +34 -0
- package/dist/pr-url-detector.js.map +1 -0
- package/dist/server/agent-server.js +69 -32
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +69 -32
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +7 -3
- package/src/adapters/claude/conversion/sdk-to-acp.ts +28 -3
- package/src/adapters/claude/types.ts +1 -0
- package/src/pr-url-detector.test.ts +140 -0
- package/src/pr-url-detector.ts +46 -0
- package/src/server/agent-server.test.ts +91 -12
- package/src/server/agent-server.ts +12 -35
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/agent",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.535",
|
|
4
4
|
"repository": "https://github.com/PostHog/code",
|
|
5
5
|
"description": "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
|
|
6
6
|
"exports": {
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
"types": "./dist/posthog-api.d.ts",
|
|
21
21
|
"import": "./dist/posthog-api.js"
|
|
22
22
|
},
|
|
23
|
+
"./pr-url-detector": {
|
|
24
|
+
"types": "./dist/pr-url-detector.d.ts",
|
|
25
|
+
"import": "./dist/pr-url-detector.js"
|
|
26
|
+
},
|
|
23
27
|
"./types": {
|
|
24
28
|
"types": "./dist/types.d.ts",
|
|
25
29
|
"import": "./dist/types.js"
|
|
@@ -103,8 +107,8 @@
|
|
|
103
107
|
"typescript": "^5.5.0",
|
|
104
108
|
"vitest": "^2.1.8",
|
|
105
109
|
"@posthog/shared": "1.0.0",
|
|
106
|
-
"@posthog/
|
|
107
|
-
"@posthog/
|
|
110
|
+
"@posthog/git": "1.0.0",
|
|
111
|
+
"@posthog/enricher": "1.0.0"
|
|
108
112
|
},
|
|
109
113
|
"dependencies": {
|
|
110
114
|
"@agentclientprotocol/sdk": "0.19.0",
|
|
@@ -90,13 +90,23 @@ function toolMeta(
|
|
|
90
90
|
toolName: string,
|
|
91
91
|
toolResponse?: unknown,
|
|
92
92
|
parentToolCallId?: string,
|
|
93
|
+
bashCommand?: string,
|
|
93
94
|
): ToolUpdateMeta {
|
|
94
95
|
const meta: ToolUpdateMeta["claudeCode"] = { toolName };
|
|
95
96
|
if (toolResponse !== undefined) meta.toolResponse = toolResponse;
|
|
96
97
|
if (parentToolCallId) meta.parentToolCallId = parentToolCallId;
|
|
98
|
+
if (bashCommand) meta.bashCommand = bashCommand;
|
|
97
99
|
return { claudeCode: meta };
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
function bashCommandFromToolUse(
|
|
103
|
+
toolUse: ToolUseCache[string] | undefined,
|
|
104
|
+
): string | undefined {
|
|
105
|
+
if (!toolUse || toolUse.name !== "Bash") return undefined;
|
|
106
|
+
const command = (toolUse.input as { command?: unknown } | undefined)?.command;
|
|
107
|
+
return typeof command === "string" ? command : undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
100
110
|
function handleTextChunk(
|
|
101
111
|
chunk: { text: string },
|
|
102
112
|
role: Role,
|
|
@@ -181,7 +191,12 @@ function handleToolUseChunk(
|
|
|
181
191
|
await ctx.client.sessionUpdate({
|
|
182
192
|
sessionId: ctx.sessionId,
|
|
183
193
|
update: {
|
|
184
|
-
_meta: toolMeta(
|
|
194
|
+
_meta: toolMeta(
|
|
195
|
+
toolUse.name,
|
|
196
|
+
toolResponse,
|
|
197
|
+
ctx.parentToolCallId,
|
|
198
|
+
bashCommandFromToolUse(toolUse),
|
|
199
|
+
),
|
|
185
200
|
toolCallId: toolUseId,
|
|
186
201
|
sessionUpdate: "tool_call_update",
|
|
187
202
|
...(editUpdate ? editUpdate : {}),
|
|
@@ -211,7 +226,12 @@ function handleToolUseChunk(
|
|
|
211
226
|
});
|
|
212
227
|
|
|
213
228
|
const meta: Record<string, unknown> = {
|
|
214
|
-
...toolMeta(
|
|
229
|
+
...toolMeta(
|
|
230
|
+
chunk.name,
|
|
231
|
+
undefined,
|
|
232
|
+
ctx.parentToolCallId,
|
|
233
|
+
bashCommandFromToolUse(chunk),
|
|
234
|
+
),
|
|
215
235
|
};
|
|
216
236
|
if (chunk.name === "Bash" && ctx.supportsTerminalOutput && !alreadyCached) {
|
|
217
237
|
meta.terminal_info = { terminal_id: chunk.id };
|
|
@@ -351,7 +371,12 @@ function handleToolResultChunk(
|
|
|
351
371
|
}
|
|
352
372
|
|
|
353
373
|
const meta: Record<string, unknown> = {
|
|
354
|
-
...toolMeta(
|
|
374
|
+
...toolMeta(
|
|
375
|
+
toolUse.name,
|
|
376
|
+
undefined,
|
|
377
|
+
ctx.parentToolCallId,
|
|
378
|
+
bashCommandFromToolUse(toolUse),
|
|
379
|
+
),
|
|
355
380
|
...(resultMeta?.terminal_exit
|
|
356
381
|
? { terminal_exit: resultMeta.terminal_exit }
|
|
357
382
|
: {}),
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
type ExtractCreatedPrUrlInput,
|
|
4
|
+
extractCreatedPrUrl,
|
|
5
|
+
} from "./pr-url-detector";
|
|
6
|
+
|
|
7
|
+
const PR_URL = "https://github.com/PostHog/posthog/pull/12345";
|
|
8
|
+
|
|
9
|
+
interface Case {
|
|
10
|
+
name: string;
|
|
11
|
+
input: ExtractCreatedPrUrlInput;
|
|
12
|
+
expected: string | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const cases: Case[] = [
|
|
16
|
+
{
|
|
17
|
+
name: "returns the URL when gh pr create produced it (string toolResponse)",
|
|
18
|
+
input: {
|
|
19
|
+
toolName: "Bash",
|
|
20
|
+
bashCommand: 'gh pr create --title "x" --body "y"',
|
|
21
|
+
toolResponse: `${PR_URL}\n`,
|
|
22
|
+
},
|
|
23
|
+
expected: PR_URL,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "returns the URL when gh pr create produced it (object toolResponse)",
|
|
27
|
+
input: {
|
|
28
|
+
toolName: "Bash",
|
|
29
|
+
bashCommand: "gh pr create --fill",
|
|
30
|
+
toolResponse: { stdout: `${PR_URL}\n`, stderr: "" },
|
|
31
|
+
},
|
|
32
|
+
expected: PR_URL,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "ignores PR URLs from gh pr view",
|
|
36
|
+
input: {
|
|
37
|
+
toolName: "Bash",
|
|
38
|
+
bashCommand: `gh pr view ${PR_URL}`,
|
|
39
|
+
toolResponse: { stdout: PR_URL },
|
|
40
|
+
},
|
|
41
|
+
expected: null,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "ignores PR URLs from gh search prs",
|
|
45
|
+
input: {
|
|
46
|
+
toolName: "Bash",
|
|
47
|
+
bashCommand: 'gh search prs "fix login"',
|
|
48
|
+
toolResponse: PR_URL,
|
|
49
|
+
},
|
|
50
|
+
expected: null,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "ignores PR URLs from gh pr list",
|
|
54
|
+
input: {
|
|
55
|
+
toolName: "Bash",
|
|
56
|
+
bashCommand: "gh pr list --json url",
|
|
57
|
+
toolResponse: PR_URL,
|
|
58
|
+
},
|
|
59
|
+
expected: null,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "returns null when bashCommand is missing",
|
|
63
|
+
input: {
|
|
64
|
+
toolName: "Bash",
|
|
65
|
+
bashCommand: undefined,
|
|
66
|
+
toolResponse: PR_URL,
|
|
67
|
+
},
|
|
68
|
+
expected: null,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "returns null for non-Bash tools",
|
|
72
|
+
input: {
|
|
73
|
+
toolName: "Edit",
|
|
74
|
+
bashCommand: "gh pr create",
|
|
75
|
+
toolResponse: PR_URL,
|
|
76
|
+
},
|
|
77
|
+
expected: null,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "accepts the lowercase 'bash' tool variant",
|
|
81
|
+
input: {
|
|
82
|
+
toolName: "bash",
|
|
83
|
+
bashCommand: "gh pr create",
|
|
84
|
+
toolResponse: PR_URL,
|
|
85
|
+
},
|
|
86
|
+
expected: PR_URL,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "returns null when output has no PR URL",
|
|
90
|
+
input: {
|
|
91
|
+
toolName: "Bash",
|
|
92
|
+
bashCommand: "gh pr create",
|
|
93
|
+
toolResponse: "no pr was created",
|
|
94
|
+
},
|
|
95
|
+
expected: null,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "finds the URL in the content array when toolResponse is empty",
|
|
99
|
+
input: {
|
|
100
|
+
toolName: "Bash",
|
|
101
|
+
bashCommand: "gh pr create --fill",
|
|
102
|
+
toolResponse: undefined,
|
|
103
|
+
content: [{ type: "text", text: `Created: ${PR_URL}` }],
|
|
104
|
+
},
|
|
105
|
+
expected: PR_URL,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "handles output field on object toolResponse",
|
|
109
|
+
input: {
|
|
110
|
+
toolName: "Bash",
|
|
111
|
+
bashCommand: "gh pr create",
|
|
112
|
+
toolResponse: { output: PR_URL },
|
|
113
|
+
},
|
|
114
|
+
expected: PR_URL,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "matches gh pr create even with a chained command",
|
|
118
|
+
input: {
|
|
119
|
+
toolName: "Bash",
|
|
120
|
+
bashCommand: "git push -u origin feat/x && gh pr create --fill",
|
|
121
|
+
toolResponse: { stdout: PR_URL },
|
|
122
|
+
},
|
|
123
|
+
expected: PR_URL,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "does not match a fake command containing 'pr create' as text",
|
|
127
|
+
input: {
|
|
128
|
+
toolName: "Bash",
|
|
129
|
+
bashCommand: "echo 'i should pr create later'",
|
|
130
|
+
toolResponse: PR_URL,
|
|
131
|
+
},
|
|
132
|
+
expected: null,
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
describe("extractCreatedPrUrl", () => {
|
|
137
|
+
it.each(cases)("$name", ({ input, expected }) => {
|
|
138
|
+
expect(extractCreatedPrUrl(input)).toBe(expected);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const PR_URL_REGEX = /https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/;
|
|
2
|
+
const GH_PR_CREATE_REGEX = /\bgh\s+pr\s+create\b/;
|
|
3
|
+
|
|
4
|
+
export interface ExtractCreatedPrUrlInput {
|
|
5
|
+
toolName: string | undefined;
|
|
6
|
+
bashCommand: string | undefined;
|
|
7
|
+
toolResponse: unknown;
|
|
8
|
+
content?: Array<{ type?: string; text?: string }>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function extractCreatedPrUrl(
|
|
12
|
+
input: ExtractCreatedPrUrlInput,
|
|
13
|
+
): string | null {
|
|
14
|
+
const { toolName, bashCommand, toolResponse, content } = input;
|
|
15
|
+
|
|
16
|
+
if (!toolName || !/bash/i.test(toolName)) return null;
|
|
17
|
+
if (!bashCommand || !GH_PR_CREATE_REGEX.test(bashCommand)) return null;
|
|
18
|
+
|
|
19
|
+
let textToSearch = "";
|
|
20
|
+
|
|
21
|
+
if (toolResponse) {
|
|
22
|
+
if (typeof toolResponse === "string") {
|
|
23
|
+
textToSearch = toolResponse;
|
|
24
|
+
} else if (typeof toolResponse === "object" && toolResponse !== null) {
|
|
25
|
+
const respObj = toolResponse as Record<string, unknown>;
|
|
26
|
+
textToSearch =
|
|
27
|
+
String(respObj.stdout || "") + String(respObj.stderr || "");
|
|
28
|
+
if (!textToSearch && respObj.output) {
|
|
29
|
+
textToSearch = String(respObj.output);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (Array.isArray(content)) {
|
|
35
|
+
for (const item of content) {
|
|
36
|
+
if (item.type === "text" && item.text) {
|
|
37
|
+
textToSearch += ` ${item.text}`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!textToSearch) return null;
|
|
43
|
+
|
|
44
|
+
const match = textToSearch.match(PR_URL_REGEX);
|
|
45
|
+
return match ? match[0] : null;
|
|
46
|
+
}
|
|
@@ -279,16 +279,27 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
279
279
|
const keepaliveCallback: { current: (() => void) | null } = {
|
|
280
280
|
current: null,
|
|
281
281
|
};
|
|
282
|
+
// Pass through to real setInterval for non-keepalive timers; otherwise
|
|
283
|
+
// unrelated internals (undici, http server, MSW) lose their periodic
|
|
284
|
+
// callbacks and can hang the test.
|
|
285
|
+
const realSetInterval = globalThis.setInterval;
|
|
282
286
|
const setIntervalSpy = vi
|
|
283
287
|
.spyOn(globalThis, "setInterval")
|
|
284
|
-
.mockImplementation(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
288
|
+
.mockImplementation(((
|
|
289
|
+
callback: (_: undefined) => void,
|
|
290
|
+
timeout?: number,
|
|
291
|
+
...args: unknown[]
|
|
292
|
+
) => {
|
|
293
|
+
if (timeout === SSE_KEEPALIVE_INTERVAL_MS) {
|
|
294
|
+
keepaliveCallback.current = () => callback(undefined);
|
|
289
295
|
return setTimeout(() => undefined, 60_000);
|
|
290
|
-
}
|
|
291
|
-
|
|
296
|
+
}
|
|
297
|
+
return (realSetInterval as (...rest: unknown[]) => unknown)(
|
|
298
|
+
callback,
|
|
299
|
+
timeout,
|
|
300
|
+
...args,
|
|
301
|
+
);
|
|
302
|
+
}) as unknown as typeof setInterval);
|
|
292
303
|
|
|
293
304
|
let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;
|
|
294
305
|
try {
|
|
@@ -307,8 +318,9 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
307
318
|
throw new Error("Expected SSE response body reader");
|
|
308
319
|
}
|
|
309
320
|
|
|
310
|
-
await vi.waitFor(
|
|
311
|
-
expect(keepaliveCallback.current).not.toBeNull(),
|
|
321
|
+
await vi.waitFor(
|
|
322
|
+
() => expect(keepaliveCallback.current).not.toBeNull(),
|
|
323
|
+
{ timeout: 10_000, interval: 50 },
|
|
312
324
|
);
|
|
313
325
|
const emitKeepalive = keepaliveCallback.current;
|
|
314
326
|
if (!emitKeepalive) {
|
|
@@ -318,7 +330,7 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
318
330
|
|
|
319
331
|
const decoder = new TextDecoder();
|
|
320
332
|
let streamText = "";
|
|
321
|
-
for (let attempts = 0; attempts <
|
|
333
|
+
for (let attempts = 0; attempts < 10; attempts++) {
|
|
322
334
|
const { done, value } = await reader.read();
|
|
323
335
|
if (done) break;
|
|
324
336
|
streamText += decoder.decode(value, { stream: true });
|
|
@@ -331,7 +343,7 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
331
343
|
await reader?.cancel();
|
|
332
344
|
setIntervalSpy.mockRestore();
|
|
333
345
|
}
|
|
334
|
-
},
|
|
346
|
+
}, 30000);
|
|
335
347
|
});
|
|
336
348
|
|
|
337
349
|
describe("POST /command", () => {
|
|
@@ -625,7 +637,7 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
625
637
|
});
|
|
626
638
|
|
|
627
639
|
describe("detectedPrUrl tracking", () => {
|
|
628
|
-
it("stores PR URL when
|
|
640
|
+
it("stores PR URL when gh pr create produces it", () => {
|
|
629
641
|
const s = createServer();
|
|
630
642
|
const payload = {
|
|
631
643
|
task_id: "test-task-id",
|
|
@@ -635,6 +647,7 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
635
647
|
_meta: {
|
|
636
648
|
claudeCode: {
|
|
637
649
|
toolName: "Bash",
|
|
650
|
+
bashCommand: 'gh pr create --title "x" --body "y"',
|
|
638
651
|
toolResponse: {
|
|
639
652
|
stdout:
|
|
640
653
|
"https://github.com/PostHog/posthog/pull/42\nCreating pull request...",
|
|
@@ -659,6 +672,7 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
659
672
|
_meta: {
|
|
660
673
|
claudeCode: {
|
|
661
674
|
toolName: "Bash",
|
|
675
|
+
bashCommand: "gh pr create",
|
|
662
676
|
toolResponse: { stdout: "just some output" },
|
|
663
677
|
},
|
|
664
678
|
},
|
|
@@ -667,6 +681,71 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
667
681
|
(s as unknown as TestableServer).detectAndAttachPrUrl(payload, update);
|
|
668
682
|
expect((s as unknown as TestableServer).detectedPrUrl).toBeNull();
|
|
669
683
|
});
|
|
684
|
+
|
|
685
|
+
it("does not attach PR URL when the bash command is gh pr view", () => {
|
|
686
|
+
const s = createServer();
|
|
687
|
+
const payload = {
|
|
688
|
+
task_id: "test-task-id",
|
|
689
|
+
run_id: "test-run-id",
|
|
690
|
+
};
|
|
691
|
+
const update = {
|
|
692
|
+
_meta: {
|
|
693
|
+
claudeCode: {
|
|
694
|
+
toolName: "Bash",
|
|
695
|
+
bashCommand: "gh pr view 42 --json url",
|
|
696
|
+
toolResponse: {
|
|
697
|
+
stdout: "https://github.com/PostHog/posthog/pull/42",
|
|
698
|
+
},
|
|
699
|
+
},
|
|
700
|
+
},
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
(s as unknown as TestableServer).detectAndAttachPrUrl(payload, update);
|
|
704
|
+
expect((s as unknown as TestableServer).detectedPrUrl).toBeNull();
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
it("does not attach PR URL when the bash command is gh search prs", () => {
|
|
708
|
+
const s = createServer();
|
|
709
|
+
const payload = {
|
|
710
|
+
task_id: "test-task-id",
|
|
711
|
+
run_id: "test-run-id",
|
|
712
|
+
};
|
|
713
|
+
const update = {
|
|
714
|
+
_meta: {
|
|
715
|
+
claudeCode: {
|
|
716
|
+
toolName: "Bash",
|
|
717
|
+
bashCommand: 'gh search prs "fix login"',
|
|
718
|
+
toolResponse: {
|
|
719
|
+
stdout: "https://github.com/PostHog/posthog/pull/42",
|
|
720
|
+
},
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
(s as unknown as TestableServer).detectAndAttachPrUrl(payload, update);
|
|
726
|
+
expect((s as unknown as TestableServer).detectedPrUrl).toBeNull();
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
it("does not attach PR URL when bashCommand is missing", () => {
|
|
730
|
+
const s = createServer();
|
|
731
|
+
const payload = {
|
|
732
|
+
task_id: "test-task-id",
|
|
733
|
+
run_id: "test-run-id",
|
|
734
|
+
};
|
|
735
|
+
const update = {
|
|
736
|
+
_meta: {
|
|
737
|
+
claudeCode: {
|
|
738
|
+
toolName: "Bash",
|
|
739
|
+
toolResponse: {
|
|
740
|
+
stdout: "https://github.com/PostHog/posthog/pull/42",
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
},
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
(s as unknown as TestableServer).detectAndAttachPrUrl(payload, update);
|
|
747
|
+
expect((s as unknown as TestableServer).detectedPrUrl).toBeNull();
|
|
748
|
+
});
|
|
670
749
|
});
|
|
671
750
|
|
|
672
751
|
describe("buildCloudSystemPrompt", () => {
|
|
@@ -29,6 +29,7 @@ import type { PermissionMode } from "../execution-mode";
|
|
|
29
29
|
import { DEFAULT_CODEX_MODEL } from "../gateway-models";
|
|
30
30
|
import { HandoffCheckpointTracker } from "../handoff-checkpoint";
|
|
31
31
|
import { PostHogAPIClient } from "../posthog-api";
|
|
32
|
+
import { extractCreatedPrUrl } from "../pr-url-detector";
|
|
32
33
|
import {
|
|
33
34
|
formatConversationForResume,
|
|
34
35
|
type ResumeState,
|
|
@@ -2131,45 +2132,21 @@ ${attributionInstructions}
|
|
|
2131
2132
|
const meta = (update?._meta as Record<string, unknown>)?.claudeCode as
|
|
2132
2133
|
| Record<string, unknown>
|
|
2133
2134
|
| undefined;
|
|
2134
|
-
const toolResponse = meta?.toolResponse;
|
|
2135
|
-
|
|
2136
|
-
// Extract text content from tool response
|
|
2137
|
-
let textToSearch = "";
|
|
2138
|
-
|
|
2139
|
-
if (toolResponse) {
|
|
2140
|
-
if (typeof toolResponse === "string") {
|
|
2141
|
-
textToSearch = toolResponse;
|
|
2142
|
-
} else if (typeof toolResponse === "object" && toolResponse !== null) {
|
|
2143
|
-
const respObj = toolResponse as Record<string, unknown>;
|
|
2144
|
-
textToSearch =
|
|
2145
|
-
String(respObj.stdout || "") + String(respObj.stderr || "");
|
|
2146
|
-
if (!textToSearch && respObj.output) {
|
|
2147
|
-
textToSearch = String(respObj.output);
|
|
2148
|
-
}
|
|
2149
|
-
}
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
// Also check content array
|
|
2153
|
-
const content = update?.content;
|
|
2154
|
-
if (Array.isArray(content)) {
|
|
2155
|
-
for (const item of content) {
|
|
2156
|
-
if (item.type === "text" && item.text) {
|
|
2157
|
-
textToSearch += ` ${item.text}`;
|
|
2158
|
-
}
|
|
2159
|
-
}
|
|
2160
|
-
}
|
|
2161
2135
|
|
|
2162
|
-
|
|
2136
|
+
const content = update?.content as
|
|
2137
|
+
| Array<{ type?: string; text?: string }>
|
|
2138
|
+
| undefined;
|
|
2163
2139
|
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2140
|
+
const prUrl = extractCreatedPrUrl({
|
|
2141
|
+
toolName: meta?.toolName as string | undefined,
|
|
2142
|
+
bashCommand: meta?.bashCommand as string | undefined,
|
|
2143
|
+
toolResponse: meta?.toolResponse,
|
|
2144
|
+
content,
|
|
2145
|
+
});
|
|
2146
|
+
if (!prUrl) return;
|
|
2169
2147
|
|
|
2170
|
-
const prUrl = prUrlMatch[0];
|
|
2171
2148
|
this.detectedPrUrl = prUrl;
|
|
2172
|
-
this.logger.debug("Detected PR URL
|
|
2149
|
+
this.logger.debug("Detected PR URL from gh pr create", {
|
|
2173
2150
|
runId: payload.run_id,
|
|
2174
2151
|
prUrl,
|
|
2175
2152
|
});
|