@posthog/agent 2.1.118 → 2.1.124
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 +45 -2
- package/dist/agent.js.map +1 -1
- package/dist/posthog-api.d.ts +1 -0
- package/dist/posthog-api.js +11 -1
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.d.ts +7 -0
- package/dist/server/agent-server.js +240 -13
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +240 -13
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude/conversion/acp-to-sdk.ts +6 -0
- package/src/adapters/claude/permissions/permission-handlers.ts +7 -1
- package/src/posthog-api.ts +15 -0
- package/src/server/agent-server.test.ts +126 -0
- package/src/server/agent-server.ts +261 -12
- package/src/server/question-relay.test.ts +363 -0
- package/src/session-log-writer.test.ts +19 -0
- package/src/session-log-writer.ts +40 -0
- package/src/test/mocks/msw-handlers.ts +25 -1
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { type SetupServerApi, setupServer } from "msw/node";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import type { PostHogAPIClient } from "../posthog-api.js";
|
|
4
|
+
import { createTestRepo, type TestRepo } from "../test/fixtures/api.js";
|
|
5
|
+
import { createPostHogHandlers } from "../test/mocks/msw-handlers.js";
|
|
6
|
+
import type { Task, TaskRun } from "../types.js";
|
|
7
|
+
import { AgentServer } from "./agent-server.js";
|
|
8
|
+
|
|
9
|
+
interface TestableAgentServer {
|
|
10
|
+
posthogAPI: PostHogAPIClient;
|
|
11
|
+
isQuestionMeta: (value: unknown) => boolean;
|
|
12
|
+
getFirstQuestionMeta: (meta: unknown) => unknown;
|
|
13
|
+
relaySlackQuestion: (payload: Record<string, unknown>, meta: unknown) => void;
|
|
14
|
+
createCloudClient: (payload: Record<string, unknown>) => {
|
|
15
|
+
requestPermission: (opts: {
|
|
16
|
+
options: unknown[];
|
|
17
|
+
toolCall: unknown;
|
|
18
|
+
}) => Promise<{
|
|
19
|
+
outcome: { outcome: string };
|
|
20
|
+
_meta?: { message?: string };
|
|
21
|
+
}>;
|
|
22
|
+
};
|
|
23
|
+
questionRelayedToSlack: boolean;
|
|
24
|
+
session: unknown;
|
|
25
|
+
relayAgentResponse: (payload: Record<string, unknown>) => Promise<void>;
|
|
26
|
+
sendInitialTaskMessage: (payload: Record<string, unknown>) => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const TEST_PAYLOAD = {
|
|
30
|
+
run_id: "test-run-id",
|
|
31
|
+
task_id: "test-task-id",
|
|
32
|
+
team_id: 1,
|
|
33
|
+
user_id: 1,
|
|
34
|
+
distinct_id: "test-distinct-id",
|
|
35
|
+
mode: "interactive" as const,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const QUESTION_META = {
|
|
39
|
+
twigToolKind: "question",
|
|
40
|
+
questions: [
|
|
41
|
+
{
|
|
42
|
+
question: "Which license should I use?",
|
|
43
|
+
options: [
|
|
44
|
+
{ label: "MIT", description: "Permissive license" },
|
|
45
|
+
{ label: "Apache 2.0", description: "Patent grant included" },
|
|
46
|
+
{ label: "GPL v3", description: "Copyleft license" },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
describe("Question relay", () => {
|
|
53
|
+
let repo: TestRepo;
|
|
54
|
+
let server: TestableAgentServer;
|
|
55
|
+
let mswServer: SetupServerApi;
|
|
56
|
+
const port = 3098;
|
|
57
|
+
|
|
58
|
+
beforeEach(async () => {
|
|
59
|
+
repo = await createTestRepo("question-relay");
|
|
60
|
+
mswServer = setupServer(
|
|
61
|
+
...createPostHogHandlers({ baseUrl: "http://localhost:8000" }),
|
|
62
|
+
);
|
|
63
|
+
mswServer.listen({ onUnhandledRequest: "bypass" });
|
|
64
|
+
|
|
65
|
+
server = new AgentServer({
|
|
66
|
+
port,
|
|
67
|
+
jwtPublicKey: "unused-in-unit-tests",
|
|
68
|
+
repositoryPath: repo.path,
|
|
69
|
+
apiUrl: "http://localhost:8000",
|
|
70
|
+
apiKey: "test-api-key",
|
|
71
|
+
projectId: 1,
|
|
72
|
+
mode: "interactive",
|
|
73
|
+
taskId: "test-task-id",
|
|
74
|
+
runId: "test-run-id",
|
|
75
|
+
}) as unknown as TestableAgentServer;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
afterEach(async () => {
|
|
79
|
+
mswServer.close();
|
|
80
|
+
await repo.cleanup();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("isQuestionMeta", () => {
|
|
84
|
+
it.each([
|
|
85
|
+
["null", null],
|
|
86
|
+
["undefined", undefined],
|
|
87
|
+
["number", 42],
|
|
88
|
+
["string", "not a question"],
|
|
89
|
+
["object without question field", { options: [] }],
|
|
90
|
+
["object with non-string question", { question: 123 }],
|
|
91
|
+
["object with non-array options", { question: "Q?", options: "bad" }],
|
|
92
|
+
[
|
|
93
|
+
"object with invalid option items",
|
|
94
|
+
{ question: "Q?", options: [{ notLabel: "x" }] },
|
|
95
|
+
],
|
|
96
|
+
])("rejects %s", (_label, value) => {
|
|
97
|
+
expect(server.isQuestionMeta(value)).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it.each([
|
|
101
|
+
[
|
|
102
|
+
"question with options",
|
|
103
|
+
{
|
|
104
|
+
question: "Pick one",
|
|
105
|
+
options: [{ label: "A", description: "desc" }, { label: "B" }],
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
["question without options", { question: "What do you think?" }],
|
|
109
|
+
["question with empty options", { question: "Confirm?", options: [] }],
|
|
110
|
+
])("accepts %s", (_label, value) => {
|
|
111
|
+
expect(server.isQuestionMeta(value)).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("getFirstQuestionMeta", () => {
|
|
116
|
+
it.each([
|
|
117
|
+
["null meta", null],
|
|
118
|
+
["undefined meta", undefined],
|
|
119
|
+
["meta without questions", { other: "field" }],
|
|
120
|
+
["meta with empty questions array", { questions: [] }],
|
|
121
|
+
["meta with non-array questions", { questions: "not-array" }],
|
|
122
|
+
])("returns null for %s", (_label, meta) => {
|
|
123
|
+
expect(server.getFirstQuestionMeta(meta)).toBeNull();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("returns first question from valid meta", () => {
|
|
127
|
+
const result = server.getFirstQuestionMeta(QUESTION_META);
|
|
128
|
+
expect(result).toEqual(QUESTION_META.questions[0]);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("relaySlackQuestion", () => {
|
|
133
|
+
it("relays formatted question with options via posthogAPI", () => {
|
|
134
|
+
const relaySpy = vi
|
|
135
|
+
.spyOn(server.posthogAPI, "relayMessage")
|
|
136
|
+
.mockResolvedValue(undefined);
|
|
137
|
+
|
|
138
|
+
server.relaySlackQuestion(TEST_PAYLOAD, QUESTION_META);
|
|
139
|
+
|
|
140
|
+
expect(relaySpy).toHaveBeenCalledOnce();
|
|
141
|
+
const [taskId, runId, message] = relaySpy.mock.calls[0];
|
|
142
|
+
expect(taskId).toBe("test-task-id");
|
|
143
|
+
expect(runId).toBe("test-run-id");
|
|
144
|
+
expect(message).toContain("*Which license should I use?*");
|
|
145
|
+
expect(message).toContain("1. *MIT*");
|
|
146
|
+
expect(message).toContain("Permissive license");
|
|
147
|
+
expect(message).toContain("2. *Apache 2.0*");
|
|
148
|
+
expect(message).toContain("3. *GPL v3*");
|
|
149
|
+
expect(message).toContain("Reply in this thread");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("sets questionRelayedToSlack flag", () => {
|
|
153
|
+
vi.spyOn(server.posthogAPI, "relayMessage").mockResolvedValue(undefined);
|
|
154
|
+
|
|
155
|
+
server.relaySlackQuestion(TEST_PAYLOAD, QUESTION_META);
|
|
156
|
+
expect(server.questionRelayedToSlack).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("does not relay when meta has no valid question", () => {
|
|
160
|
+
const relaySpy = vi
|
|
161
|
+
.spyOn(server.posthogAPI, "relayMessage")
|
|
162
|
+
.mockResolvedValue(undefined);
|
|
163
|
+
|
|
164
|
+
server.relaySlackQuestion(TEST_PAYLOAD, { twigToolKind: "question" });
|
|
165
|
+
expect(server.questionRelayedToSlack).toBe(false);
|
|
166
|
+
expect(relaySpy).not.toHaveBeenCalled();
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("createCloudClient requestPermission", () => {
|
|
171
|
+
const ALLOW_OPTIONS = [
|
|
172
|
+
{ kind: "allow_once", optionId: "allow", name: "Allow" },
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
describe("with TWIG_INTERACTION_ORIGIN=slack", () => {
|
|
176
|
+
beforeEach(() => {
|
|
177
|
+
process.env.TWIG_INTERACTION_ORIGIN = "slack";
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
afterEach(() => {
|
|
181
|
+
delete process.env.TWIG_INTERACTION_ORIGIN;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("returns cancelled with relay message for question tool", async () => {
|
|
185
|
+
vi.spyOn(server.posthogAPI, "relayMessage").mockResolvedValue(
|
|
186
|
+
undefined,
|
|
187
|
+
);
|
|
188
|
+
const client = server.createCloudClient(TEST_PAYLOAD);
|
|
189
|
+
|
|
190
|
+
const result = await client.requestPermission({
|
|
191
|
+
options: ALLOW_OPTIONS,
|
|
192
|
+
toolCall: { _meta: QUESTION_META },
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
expect(result.outcome.outcome).toBe("cancelled");
|
|
196
|
+
expect(result._meta?.message).toContain("relayed to the Slack thread");
|
|
197
|
+
expect(result._meta?.message).toContain("Do NOT re-ask the question");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("auto-approves non-question tools", async () => {
|
|
201
|
+
const client = server.createCloudClient(TEST_PAYLOAD);
|
|
202
|
+
|
|
203
|
+
const result = await client.requestPermission({
|
|
204
|
+
options: ALLOW_OPTIONS,
|
|
205
|
+
toolCall: { _meta: { twigToolKind: "bash" } },
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
expect(result.outcome.outcome).toBe("selected");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("auto-approves tools without meta", async () => {
|
|
212
|
+
const client = server.createCloudClient(TEST_PAYLOAD);
|
|
213
|
+
|
|
214
|
+
const result = await client.requestPermission({
|
|
215
|
+
options: ALLOW_OPTIONS,
|
|
216
|
+
toolCall: { _meta: null },
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
expect(result.outcome.outcome).toBe("selected");
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe("without TWIG_INTERACTION_ORIGIN", () => {
|
|
224
|
+
beforeEach(() => {
|
|
225
|
+
delete process.env.TWIG_INTERACTION_ORIGIN;
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("auto-approves question tools (no Slack relay)", async () => {
|
|
229
|
+
const client = server.createCloudClient(TEST_PAYLOAD);
|
|
230
|
+
|
|
231
|
+
const result = await client.requestPermission({
|
|
232
|
+
options: ALLOW_OPTIONS,
|
|
233
|
+
toolCall: { _meta: QUESTION_META },
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
expect(result.outcome.outcome).toBe("selected");
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe("relayAgentResponse duplicate suppression", () => {
|
|
242
|
+
it("skips relay when questionRelayedToSlack is set", async () => {
|
|
243
|
+
const relaySpy = vi
|
|
244
|
+
.spyOn(server.posthogAPI, "relayMessage")
|
|
245
|
+
.mockResolvedValue(undefined);
|
|
246
|
+
|
|
247
|
+
server.session = {
|
|
248
|
+
payload: TEST_PAYLOAD,
|
|
249
|
+
logWriter: {
|
|
250
|
+
flush: vi.fn().mockResolvedValue(undefined),
|
|
251
|
+
getLastAgentMessage: vi.fn().mockReturnValue("agent response"),
|
|
252
|
+
isRegistered: vi.fn().mockReturnValue(true),
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
server.questionRelayedToSlack = true;
|
|
257
|
+
await server.relayAgentResponse(TEST_PAYLOAD);
|
|
258
|
+
|
|
259
|
+
expect(server.questionRelayedToSlack).toBe(false);
|
|
260
|
+
expect(relaySpy).not.toHaveBeenCalled();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("relays normally when questionRelayedToSlack is not set", async () => {
|
|
264
|
+
const relaySpy = vi
|
|
265
|
+
.spyOn(server.posthogAPI, "relayMessage")
|
|
266
|
+
.mockResolvedValue(undefined);
|
|
267
|
+
|
|
268
|
+
server.session = {
|
|
269
|
+
payload: TEST_PAYLOAD,
|
|
270
|
+
logWriter: {
|
|
271
|
+
flush: vi.fn().mockResolvedValue(undefined),
|
|
272
|
+
getLastAgentMessage: vi.fn().mockReturnValue("agent response"),
|
|
273
|
+
isRegistered: vi.fn().mockReturnValue(true),
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
server.questionRelayedToSlack = false;
|
|
278
|
+
await server.relayAgentResponse(TEST_PAYLOAD);
|
|
279
|
+
|
|
280
|
+
expect(relaySpy).toHaveBeenCalledWith(
|
|
281
|
+
"test-task-id",
|
|
282
|
+
"test-run-id",
|
|
283
|
+
"agent response",
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("does not relay when no agent message is available", async () => {
|
|
288
|
+
const relaySpy = vi
|
|
289
|
+
.spyOn(server.posthogAPI, "relayMessage")
|
|
290
|
+
.mockResolvedValue(undefined);
|
|
291
|
+
|
|
292
|
+
server.session = {
|
|
293
|
+
payload: TEST_PAYLOAD,
|
|
294
|
+
logWriter: {
|
|
295
|
+
flush: vi.fn().mockResolvedValue(undefined),
|
|
296
|
+
getLastAgentMessage: vi.fn().mockReturnValue(null),
|
|
297
|
+
isRegistered: vi.fn().mockReturnValue(true),
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
server.questionRelayedToSlack = false;
|
|
302
|
+
await server.relayAgentResponse(TEST_PAYLOAD);
|
|
303
|
+
|
|
304
|
+
expect(relaySpy).not.toHaveBeenCalled();
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe("sendInitialTaskMessage prompt source", () => {
|
|
309
|
+
it("uses run state initial_prompt_override when present", async () => {
|
|
310
|
+
vi.spyOn(server.posthogAPI, "getTask").mockResolvedValue({
|
|
311
|
+
id: "test-task-id",
|
|
312
|
+
title: "t",
|
|
313
|
+
description: "original task description",
|
|
314
|
+
} as unknown as Task);
|
|
315
|
+
vi.spyOn(server.posthogAPI, "getTaskRun").mockResolvedValue({
|
|
316
|
+
id: "test-run-id",
|
|
317
|
+
task: "test-task-id",
|
|
318
|
+
state: { initial_prompt_override: "override instruction" },
|
|
319
|
+
} as unknown as TaskRun);
|
|
320
|
+
|
|
321
|
+
const promptSpy = vi.fn().mockResolvedValue({ stopReason: "max_tokens" });
|
|
322
|
+
server.session = {
|
|
323
|
+
payload: TEST_PAYLOAD,
|
|
324
|
+
acpSessionId: "acp-session",
|
|
325
|
+
clientConnection: { prompt: promptSpy },
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
await server.sendInitialTaskMessage(TEST_PAYLOAD);
|
|
329
|
+
|
|
330
|
+
expect(promptSpy).toHaveBeenCalledWith({
|
|
331
|
+
sessionId: "acp-session",
|
|
332
|
+
prompt: [{ type: "text", text: "override instruction" }],
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("falls back to task description when override is missing", async () => {
|
|
337
|
+
vi.spyOn(server.posthogAPI, "getTask").mockResolvedValue({
|
|
338
|
+
id: "test-task-id",
|
|
339
|
+
title: "t",
|
|
340
|
+
description: "original task description",
|
|
341
|
+
} as unknown as Task);
|
|
342
|
+
vi.spyOn(server.posthogAPI, "getTaskRun").mockResolvedValue({
|
|
343
|
+
id: "test-run-id",
|
|
344
|
+
task: "test-task-id",
|
|
345
|
+
state: {},
|
|
346
|
+
} as unknown as TaskRun);
|
|
347
|
+
|
|
348
|
+
const promptSpy = vi.fn().mockResolvedValue({ stopReason: "max_tokens" });
|
|
349
|
+
server.session = {
|
|
350
|
+
payload: TEST_PAYLOAD,
|
|
351
|
+
acpSessionId: "acp-session",
|
|
352
|
+
clientConnection: { prompt: promptSpy },
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
await server.sendInitialTaskMessage(TEST_PAYLOAD);
|
|
356
|
+
|
|
357
|
+
expect(promptSpy).toHaveBeenCalledWith({
|
|
358
|
+
sessionId: "acp-session",
|
|
359
|
+
prompt: [{ type: "text", text: "original task description" }],
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
});
|
|
@@ -137,6 +137,25 @@ describe("SessionLogWriter", () => {
|
|
|
137
137
|
sessionUpdate: "agent_message",
|
|
138
138
|
content: { type: "text", text: "Hello world" },
|
|
139
139
|
});
|
|
140
|
+
expect(logWriter.getLastAgentMessage(sessionId)).toBe("Hello world");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("tracks direct agent_message updates", async () => {
|
|
144
|
+
const sessionId = "s1";
|
|
145
|
+
logWriter.register(sessionId, { taskId: "t1", runId: sessionId });
|
|
146
|
+
|
|
147
|
+
logWriter.appendRawLine(
|
|
148
|
+
sessionId,
|
|
149
|
+
makeSessionUpdate("agent_message", {
|
|
150
|
+
content: { type: "text", text: "Pick MIT or Apache" },
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
await logWriter.flush(sessionId);
|
|
155
|
+
|
|
156
|
+
expect(logWriter.getLastAgentMessage(sessionId)).toBe(
|
|
157
|
+
"Pick MIT or Apache",
|
|
158
|
+
);
|
|
140
159
|
});
|
|
141
160
|
});
|
|
142
161
|
|
|
@@ -22,6 +22,7 @@ interface ChunkBuffer {
|
|
|
22
22
|
interface SessionState {
|
|
23
23
|
context: SessionContext;
|
|
24
24
|
chunkBuffer?: ChunkBuffer;
|
|
25
|
+
lastAgentMessage?: string;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export class SessionLogWriter {
|
|
@@ -145,6 +146,11 @@ export class SessionLogWriter {
|
|
|
145
146
|
// Non-chunk event: flush any buffered chunks first
|
|
146
147
|
this.emitCoalescedMessage(sessionId, session);
|
|
147
148
|
|
|
149
|
+
const nonChunkAgentText = this.extractAgentMessageText(message);
|
|
150
|
+
if (nonChunkAgentText) {
|
|
151
|
+
session.lastAgentMessage = nonChunkAgentText;
|
|
152
|
+
}
|
|
153
|
+
|
|
148
154
|
const entry: StoredNotification = {
|
|
149
155
|
type: "notification",
|
|
150
156
|
timestamp,
|
|
@@ -260,6 +266,7 @@ export class SessionLogWriter {
|
|
|
260
266
|
|
|
261
267
|
const { text, firstTimestamp } = session.chunkBuffer;
|
|
262
268
|
session.chunkBuffer = undefined;
|
|
269
|
+
session.lastAgentMessage = text;
|
|
263
270
|
|
|
264
271
|
const entry: StoredNotification = {
|
|
265
272
|
type: "notification",
|
|
@@ -286,6 +293,39 @@ export class SessionLogWriter {
|
|
|
286
293
|
}
|
|
287
294
|
}
|
|
288
295
|
|
|
296
|
+
getLastAgentMessage(sessionId: string): string | undefined {
|
|
297
|
+
return this.sessions.get(sessionId)?.lastAgentMessage;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private extractAgentMessageText(
|
|
301
|
+
message: Record<string, unknown>,
|
|
302
|
+
): string | null {
|
|
303
|
+
if (message.method !== "session/update") {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const params = message.params as Record<string, unknown> | undefined;
|
|
308
|
+
const update = params?.update as Record<string, unknown> | undefined;
|
|
309
|
+
if (update?.sessionUpdate !== "agent_message") {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const content = update.content as
|
|
314
|
+
| { type?: string; text?: string }
|
|
315
|
+
| undefined;
|
|
316
|
+
if (content?.type === "text" && typeof content.text === "string") {
|
|
317
|
+
const trimmed = content.text.trim();
|
|
318
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (typeof update.message === "string") {
|
|
322
|
+
const trimmed = update.message.trim();
|
|
323
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
289
329
|
private scheduleFlush(sessionId: string): void {
|
|
290
330
|
const existing = this.flushTimeouts.get(sessionId);
|
|
291
331
|
if (existing) clearTimeout(existing);
|
|
@@ -5,6 +5,7 @@ type AnyHttpResponse = Response | ReturnType<typeof HttpResponse.json>;
|
|
|
5
5
|
export interface PostHogHandlersOptions {
|
|
6
6
|
baseUrl?: string;
|
|
7
7
|
onAppendLog?: (entries: unknown[]) => void;
|
|
8
|
+
getTask?: () => unknown;
|
|
8
9
|
getTaskRun?: () => unknown;
|
|
9
10
|
appendLogResponse?: () => AnyHttpResponse;
|
|
10
11
|
}
|
|
@@ -13,6 +14,7 @@ export function createPostHogHandlers(options: PostHogHandlersOptions = {}) {
|
|
|
13
14
|
const {
|
|
14
15
|
baseUrl = "http://localhost:8000",
|
|
15
16
|
onAppendLog,
|
|
17
|
+
getTask,
|
|
16
18
|
getTaskRun,
|
|
17
19
|
appendLogResponse,
|
|
18
20
|
} = options;
|
|
@@ -33,13 +35,35 @@ export function createPostHogHandlers(options: PostHogHandlersOptions = {}) {
|
|
|
33
35
|
},
|
|
34
36
|
),
|
|
35
37
|
|
|
38
|
+
// GET /tasks/:taskId - Fetch task details
|
|
39
|
+
http.get(`${baseUrl}/api/projects/:projectId/tasks/:taskId/`, () => {
|
|
40
|
+
const task = getTask?.() ?? {
|
|
41
|
+
id: "test-task-id",
|
|
42
|
+
title: "Test task",
|
|
43
|
+
description: null,
|
|
44
|
+
origin_product: "user_created",
|
|
45
|
+
repository: "test/repo",
|
|
46
|
+
created_at: new Date().toISOString(),
|
|
47
|
+
updated_at: new Date().toISOString(),
|
|
48
|
+
};
|
|
49
|
+
return HttpResponse.json(task);
|
|
50
|
+
}),
|
|
51
|
+
|
|
36
52
|
// GET /runs/:runId - Fetch task run details
|
|
37
53
|
http.get(
|
|
38
|
-
`${baseUrl}/api/projects/:projectId/tasks/:taskId/runs/:runId
|
|
54
|
+
`${baseUrl}/api/projects/:projectId/tasks/:taskId/runs/:runId/`,
|
|
39
55
|
() => {
|
|
40
56
|
const taskRun = getTaskRun?.() ?? { log_url: "" };
|
|
41
57
|
return HttpResponse.json(taskRun);
|
|
42
58
|
},
|
|
43
59
|
),
|
|
60
|
+
|
|
61
|
+
// PATCH /runs/:runId - Update task run
|
|
62
|
+
http.patch(
|
|
63
|
+
`${baseUrl}/api/projects/:projectId/tasks/:taskId/runs/:runId/`,
|
|
64
|
+
() => {
|
|
65
|
+
return HttpResponse.json({});
|
|
66
|
+
},
|
|
67
|
+
),
|
|
44
68
|
];
|
|
45
69
|
}
|