@posthog/agent 2.3.326 → 2.3.346
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 +9 -0
- package/dist/adapters/claude/conversion/tool-use-to-acp.js +15 -1
- package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -1
- package/dist/adapters/claude/permissions/permission-options.js +18 -11
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
- package/dist/adapters/claude/session/jsonl-hydration.js.map +1 -1
- package/dist/adapters/claude/session/models.d.ts +2 -2
- package/dist/adapters/claude/session/models.js +12 -6
- package/dist/adapters/claude/session/models.js.map +1 -1
- package/dist/adapters/claude/tools.js +15 -13
- package/dist/adapters/claude/tools.js.map +1 -1
- package/dist/adapters/reasoning-effort.d.ts +1 -1
- package/dist/adapters/reasoning-effort.js +11 -5
- package/dist/adapters/reasoning-effort.js.map +1 -1
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +6975 -613
- package/dist/agent.js.map +1 -1
- package/dist/execution-mode.d.ts +1 -1
- package/dist/execution-mode.js +14 -12
- package/dist/execution-mode.js.map +1 -1
- package/dist/posthog-api.d.ts +5 -3
- package/dist/posthog-api.js +12 -22
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.d.ts +8 -1
- package/dist/server/agent-server.js +11362 -4894
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +11423 -4954
- package/dist/server/bin.cjs.map +1 -1
- package/dist/types.d.ts +11 -1
- package/package.json +7 -6
- package/src/adapters/acp-connection.ts +14 -1
- package/src/adapters/claude/UPSTREAM.md +24 -4
- package/src/adapters/claude/claude-agent.ts +161 -14
- package/src/adapters/claude/conversion/acp-to-sdk.test.ts +49 -0
- package/src/adapters/claude/conversion/acp-to-sdk.ts +23 -6
- package/src/adapters/claude/conversion/sdk-to-acp.ts +14 -2
- package/src/adapters/claude/conversion/tool-use-to-acp.ts +18 -1
- package/src/adapters/claude/hooks.test.ts +189 -0
- package/src/adapters/claude/hooks.ts +93 -3
- package/src/adapters/claude/permissions/permission-handlers.ts +2 -1
- package/src/adapters/claude/permissions/permission-options.ts +5 -0
- package/src/adapters/claude/session/models.ts +11 -5
- package/src/adapters/claude/session/options.ts +19 -3
- package/src/adapters/claude/session/settings.ts +17 -9
- package/src/adapters/claude/tools.ts +1 -1
- package/src/adapters/claude/types.ts +8 -1
- package/src/adapters/codex/codex-agent.ts +15 -2
- package/src/adapters/codex/codex-client.test.ts +112 -0
- package/src/adapters/codex/codex-client.ts +14 -1
- package/src/adapters/reasoning-effort.ts +6 -1
- package/src/agent.ts +6 -0
- package/src/enrichment/file-enricher.test.ts +163 -0
- package/src/enrichment/file-enricher.ts +82 -0
- package/src/execution-mode.test.ts +1 -0
- package/src/execution-mode.ts +13 -11
- package/src/posthog-api.test.ts +32 -0
- package/src/posthog-api.ts +13 -30
- package/src/server/agent-server.test.ts +96 -0
- package/src/server/agent-server.ts +207 -11
- package/src/server/bin.ts +1 -1
- package/src/server/schemas.test.ts +10 -0
- package/src/server/schemas.ts +25 -6
- package/src/server/types.ts +1 -1
- package/src/test/mocks/msw-handlers.ts +4 -1
- package/src/types.ts +10 -1
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
describe,
|
|
9
9
|
expect,
|
|
10
10
|
it,
|
|
11
|
+
vi,
|
|
11
12
|
} from "vitest";
|
|
12
13
|
import { createTestRepo, type TestRepo } from "../test/fixtures/api";
|
|
13
14
|
import { createPostHogHandlers } from "../test/mocks/msw-handlers";
|
|
@@ -17,6 +18,11 @@ import { type JwtPayload, SANDBOX_CONNECTION_AUDIENCE } from "./jwt";
|
|
|
17
18
|
|
|
18
19
|
interface TestableServer {
|
|
19
20
|
getInitialPromptOverride(run: TaskRun): string | null;
|
|
21
|
+
getClearedPendingUserState(run: TaskRun | null): string[] | null;
|
|
22
|
+
clearPendingInitialPromptState(
|
|
23
|
+
payload: JwtPayload,
|
|
24
|
+
run: TaskRun | null,
|
|
25
|
+
): Promise<void>;
|
|
20
26
|
detectAndAttachPrUrl(payload: unknown, update: unknown): void;
|
|
21
27
|
detectedPrUrl: string | null;
|
|
22
28
|
buildCloudSystemPrompt(prUrl?: string | null): string;
|
|
@@ -294,6 +300,36 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
294
300
|
const body = await response.json();
|
|
295
301
|
expect(body.error).toBe("No active session for this run");
|
|
296
302
|
}, 20000);
|
|
303
|
+
|
|
304
|
+
it("accepts artifact-only user_message payloads", async () => {
|
|
305
|
+
await createServer().start();
|
|
306
|
+
const token = createToken({ run_id: "different-run-id" });
|
|
307
|
+
|
|
308
|
+
const response = await fetch(`http://localhost:${port}/command`, {
|
|
309
|
+
method: "POST",
|
|
310
|
+
headers: {
|
|
311
|
+
Authorization: `Bearer ${token}`,
|
|
312
|
+
"Content-Type": "application/json",
|
|
313
|
+
},
|
|
314
|
+
body: JSON.stringify({
|
|
315
|
+
jsonrpc: "2.0",
|
|
316
|
+
method: "user_message",
|
|
317
|
+
params: {
|
|
318
|
+
artifacts: [
|
|
319
|
+
{
|
|
320
|
+
id: "artifact-1",
|
|
321
|
+
name: "test.txt",
|
|
322
|
+
storage_path: "tasks/artifacts/test.txt",
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
},
|
|
326
|
+
}),
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
expect(response.status).toBe(400);
|
|
330
|
+
const body = await response.json();
|
|
331
|
+
expect(body.error).toBe("No active session for this run");
|
|
332
|
+
}, 20000);
|
|
297
333
|
});
|
|
298
334
|
|
|
299
335
|
describe("404 handling", () => {
|
|
@@ -350,6 +386,66 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
350
386
|
);
|
|
351
387
|
expect(result).toBeNull();
|
|
352
388
|
});
|
|
389
|
+
|
|
390
|
+
it("removes pending prompt keys when clearing initial prompt state", async () => {
|
|
391
|
+
const s = createServer();
|
|
392
|
+
const updateTaskRun = vi
|
|
393
|
+
.spyOn(
|
|
394
|
+
(
|
|
395
|
+
s as unknown as {
|
|
396
|
+
posthogAPI: {
|
|
397
|
+
updateTaskRun: (...args: unknown[]) => Promise<unknown>;
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
).posthogAPI,
|
|
401
|
+
"updateTaskRun",
|
|
402
|
+
)
|
|
403
|
+
.mockResolvedValue({} as never);
|
|
404
|
+
const run = {
|
|
405
|
+
id: "test-run-id",
|
|
406
|
+
task: "test-task-id",
|
|
407
|
+
state: {
|
|
408
|
+
sandbox_url: "https://sandbox.example.com",
|
|
409
|
+
sandbox_connect_token: "token",
|
|
410
|
+
pending_user_message: "read this",
|
|
411
|
+
pending_user_artifact_ids: ["artifact-1"],
|
|
412
|
+
pending_user_message_ts: "123.456",
|
|
413
|
+
},
|
|
414
|
+
} as unknown as TaskRun;
|
|
415
|
+
|
|
416
|
+
const nextState = (
|
|
417
|
+
s as unknown as TestableServer
|
|
418
|
+
).getClearedPendingUserState(run);
|
|
419
|
+
expect(nextState).toEqual([
|
|
420
|
+
"pending_user_message",
|
|
421
|
+
"pending_user_artifact_ids",
|
|
422
|
+
"pending_user_message_ts",
|
|
423
|
+
]);
|
|
424
|
+
|
|
425
|
+
await (s as unknown as TestableServer).clearPendingInitialPromptState(
|
|
426
|
+
{
|
|
427
|
+
run_id: "test-run-id",
|
|
428
|
+
task_id: "test-task-id",
|
|
429
|
+
team_id: 1,
|
|
430
|
+
user_id: 1,
|
|
431
|
+
distinct_id: "test-distinct-id",
|
|
432
|
+
mode: "interactive",
|
|
433
|
+
},
|
|
434
|
+
run,
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
expect(updateTaskRun).toHaveBeenCalledWith(
|
|
438
|
+
"test-task-id",
|
|
439
|
+
"test-run-id",
|
|
440
|
+
{
|
|
441
|
+
state_remove_keys: [
|
|
442
|
+
"pending_user_message",
|
|
443
|
+
"pending_user_artifact_ids",
|
|
444
|
+
"pending_user_message_ts",
|
|
445
|
+
],
|
|
446
|
+
},
|
|
447
|
+
);
|
|
448
|
+
});
|
|
353
449
|
});
|
|
354
450
|
|
|
355
451
|
describe("runtime adapter selection", () => {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
1
4
|
import type {
|
|
2
5
|
ContentBlock,
|
|
3
6
|
RequestPermissionRequest,
|
|
@@ -33,13 +36,14 @@ import type {
|
|
|
33
36
|
DeviceInfo,
|
|
34
37
|
LogLevel,
|
|
35
38
|
TaskRun,
|
|
39
|
+
TaskRunArtifact,
|
|
36
40
|
TreeSnapshotEvent,
|
|
37
41
|
} from "../types";
|
|
42
|
+
import { resourceLink } from "../utils/acp-content";
|
|
38
43
|
import { AsyncMutex } from "../utils/async-mutex";
|
|
39
44
|
import { getLlmGatewayUrl } from "../utils/gateway";
|
|
40
45
|
import { Logger } from "../utils/logger";
|
|
41
46
|
import {
|
|
42
|
-
deserializeCloudPrompt,
|
|
43
47
|
normalizeCloudPromptContent,
|
|
44
48
|
promptBlocksToText,
|
|
45
49
|
} from "./cloud-prompt";
|
|
@@ -340,7 +344,7 @@ export class AgentServer {
|
|
|
340
344
|
});
|
|
341
345
|
},
|
|
342
346
|
cancel: () => {
|
|
343
|
-
this.logger.
|
|
347
|
+
this.logger.debug("SSE connection closed");
|
|
344
348
|
if (this.session?.sseController) {
|
|
345
349
|
this.session.sseController = null;
|
|
346
350
|
}
|
|
@@ -545,9 +549,29 @@ export class AgentServer {
|
|
|
545
549
|
switch (method) {
|
|
546
550
|
case POSTHOG_NOTIFICATIONS.USER_MESSAGE:
|
|
547
551
|
case "user_message": {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
552
|
+
this.logger.info("Received user_message command", {
|
|
553
|
+
hasContent:
|
|
554
|
+
typeof params.content === "string"
|
|
555
|
+
? params.content.trim().length > 0
|
|
556
|
+
: Array.isArray(params.content) && params.content.length > 0,
|
|
557
|
+
artifactCount: Array.isArray(params.artifacts)
|
|
558
|
+
? params.artifacts.length
|
|
559
|
+
: 0,
|
|
560
|
+
});
|
|
561
|
+
const prompt = await this.buildPromptFromContentAndArtifacts({
|
|
562
|
+
content: params.content as string | ContentBlock[] | undefined,
|
|
563
|
+
artifacts: Array.isArray(params.artifacts)
|
|
564
|
+
? (params.artifacts as TaskRunArtifact[])
|
|
565
|
+
: [],
|
|
566
|
+
taskId: this.session.payload.task_id,
|
|
567
|
+
runId: this.session.payload.run_id,
|
|
568
|
+
});
|
|
569
|
+
if (prompt.length === 0) {
|
|
570
|
+
throw new Error("User message cannot be empty");
|
|
571
|
+
}
|
|
572
|
+
this.logger.info("Built user_message prompt", {
|
|
573
|
+
blockTypes: prompt.map((block) => block.type),
|
|
574
|
+
});
|
|
551
575
|
const promptPreview = promptBlocksToText(prompt);
|
|
552
576
|
|
|
553
577
|
this.logger.info(
|
|
@@ -1014,7 +1038,7 @@ export class AgentServer {
|
|
|
1014
1038
|
const initialPromptOverride = taskRun
|
|
1015
1039
|
? this.getInitialPromptOverride(taskRun)
|
|
1016
1040
|
: null;
|
|
1017
|
-
const pendingUserPrompt = this.getPendingUserPrompt(taskRun);
|
|
1041
|
+
const pendingUserPrompt = await this.getPendingUserPrompt(taskRun);
|
|
1018
1042
|
let initialPrompt: ContentBlock[] = [];
|
|
1019
1043
|
if (pendingUserPrompt?.length) {
|
|
1020
1044
|
initialPrompt = pendingUserPrompt;
|
|
@@ -1047,6 +1071,8 @@ export class AgentServer {
|
|
|
1047
1071
|
stopReason: result.stopReason,
|
|
1048
1072
|
});
|
|
1049
1073
|
|
|
1074
|
+
await this.clearPendingInitialPromptState(payload, taskRun);
|
|
1075
|
+
|
|
1050
1076
|
if (result.stopReason === "end_turn") {
|
|
1051
1077
|
void this.syncCloudBranchMetadata(payload);
|
|
1052
1078
|
}
|
|
@@ -1078,7 +1104,7 @@ export class AgentServer {
|
|
|
1078
1104
|
|
|
1079
1105
|
// Read the pending user prompt from TaskRun state (set by the workflow
|
|
1080
1106
|
// when the user sends a follow-up message that triggers a resume).
|
|
1081
|
-
const pendingUserPrompt = this.getPendingUserPrompt(taskRun);
|
|
1107
|
+
const pendingUserPrompt = await this.getPendingUserPrompt(taskRun);
|
|
1082
1108
|
|
|
1083
1109
|
const sandboxContext = this.resumeState.snapshotApplied
|
|
1084
1110
|
? `The workspace environment (all files, packages, and code changes) has been fully restored from where you left off.`
|
|
@@ -1218,16 +1244,186 @@ export class AgentServer {
|
|
|
1218
1244
|
return trimmed.length > 0 ? trimmed : null;
|
|
1219
1245
|
}
|
|
1220
1246
|
|
|
1221
|
-
private getPendingUserPrompt(
|
|
1247
|
+
private async getPendingUserPrompt(
|
|
1248
|
+
taskRun: TaskRun | null,
|
|
1249
|
+
): Promise<ContentBlock[] | null> {
|
|
1222
1250
|
if (!taskRun) return null;
|
|
1223
1251
|
const state = taskRun.state as Record<string, unknown> | undefined;
|
|
1224
1252
|
const message = state?.pending_user_message;
|
|
1225
|
-
|
|
1253
|
+
const artifactIds = Array.isArray(state?.pending_user_artifact_ids)
|
|
1254
|
+
? state.pending_user_artifact_ids.filter(
|
|
1255
|
+
(artifactId): artifactId is string =>
|
|
1256
|
+
typeof artifactId === "string" && artifactId.trim().length > 0,
|
|
1257
|
+
)
|
|
1258
|
+
: [];
|
|
1259
|
+
const prompt = await this.buildPromptFromContentAndArtifacts({
|
|
1260
|
+
content: typeof message === "string" ? message : undefined,
|
|
1261
|
+
artifacts: this.getArtifactsById(taskRun.artifacts, artifactIds),
|
|
1262
|
+
taskId: taskRun.task,
|
|
1263
|
+
runId: taskRun.id,
|
|
1264
|
+
});
|
|
1265
|
+
this.logger.info("Built pending user prompt", {
|
|
1266
|
+
hasMessage: typeof message === "string" && message.trim().length > 0,
|
|
1267
|
+
requestedArtifactCount: artifactIds.length,
|
|
1268
|
+
blockTypes: prompt.map((block) => block.type),
|
|
1269
|
+
});
|
|
1270
|
+
return prompt.length > 0 ? prompt : null;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
private getClearedPendingUserState(taskRun: TaskRun | null): string[] | null {
|
|
1274
|
+
const state =
|
|
1275
|
+
taskRun?.state && typeof taskRun.state === "object"
|
|
1276
|
+
? (taskRun.state as Record<string, unknown>)
|
|
1277
|
+
: null;
|
|
1278
|
+
if (!state) {
|
|
1226
1279
|
return null;
|
|
1227
1280
|
}
|
|
1228
1281
|
|
|
1229
|
-
const
|
|
1230
|
-
|
|
1282
|
+
const pendingKeys = [
|
|
1283
|
+
"pending_user_message",
|
|
1284
|
+
"pending_user_artifact_ids",
|
|
1285
|
+
"pending_user_message_ts",
|
|
1286
|
+
].filter((key) => key in state);
|
|
1287
|
+
|
|
1288
|
+
return pendingKeys.length > 0 ? pendingKeys : null;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
private async clearPendingInitialPromptState(
|
|
1292
|
+
payload: JwtPayload,
|
|
1293
|
+
taskRun: TaskRun | null,
|
|
1294
|
+
): Promise<void> {
|
|
1295
|
+
const stateRemoveKeys = this.getClearedPendingUserState(taskRun);
|
|
1296
|
+
if (!stateRemoveKeys) {
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
await this.posthogAPI.updateTaskRun(payload.task_id, payload.run_id, {
|
|
1301
|
+
state_remove_keys: stateRemoveKeys,
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
private async buildPromptFromContentAndArtifacts({
|
|
1306
|
+
content,
|
|
1307
|
+
artifacts,
|
|
1308
|
+
taskId,
|
|
1309
|
+
runId,
|
|
1310
|
+
}: {
|
|
1311
|
+
content?: string | ContentBlock[];
|
|
1312
|
+
artifacts?: TaskRunArtifact[];
|
|
1313
|
+
taskId: string;
|
|
1314
|
+
runId: string;
|
|
1315
|
+
}): Promise<ContentBlock[]> {
|
|
1316
|
+
const contentBlocks = content ? normalizeCloudPromptContent(content) : [];
|
|
1317
|
+
const artifactBlocks = await this.hydrateArtifactsToPrompt(
|
|
1318
|
+
taskId,
|
|
1319
|
+
runId,
|
|
1320
|
+
artifacts ?? [],
|
|
1321
|
+
);
|
|
1322
|
+
|
|
1323
|
+
return [...contentBlocks, ...artifactBlocks];
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
private getArtifactsById(
|
|
1327
|
+
artifacts: TaskRunArtifact[] | undefined,
|
|
1328
|
+
artifactIds: string[],
|
|
1329
|
+
): TaskRunArtifact[] {
|
|
1330
|
+
if (!artifacts?.length || artifactIds.length === 0) {
|
|
1331
|
+
return [];
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
const artifactsById = new Map(
|
|
1335
|
+
artifacts
|
|
1336
|
+
.filter(
|
|
1337
|
+
(artifact): artifact is TaskRunArtifact & { id: string } =>
|
|
1338
|
+
typeof artifact.id === "string" && artifact.id.trim().length > 0,
|
|
1339
|
+
)
|
|
1340
|
+
.map((artifact) => [artifact.id, artifact]),
|
|
1341
|
+
);
|
|
1342
|
+
|
|
1343
|
+
return artifactIds.flatMap((artifactId) => {
|
|
1344
|
+
const artifact = artifactsById.get(artifactId);
|
|
1345
|
+
if (!artifact) {
|
|
1346
|
+
this.logger.warn("Pending artifact missing from run manifest", {
|
|
1347
|
+
artifactId,
|
|
1348
|
+
});
|
|
1349
|
+
return [];
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
return [artifact];
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
private async hydrateArtifactsToPrompt(
|
|
1357
|
+
taskId: string,
|
|
1358
|
+
runId: string,
|
|
1359
|
+
artifacts: TaskRunArtifact[],
|
|
1360
|
+
): Promise<ContentBlock[]> {
|
|
1361
|
+
if (artifacts.length === 0) {
|
|
1362
|
+
return [];
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
this.logger.debug("Hydrating prompt artifacts", {
|
|
1366
|
+
taskId,
|
|
1367
|
+
runId,
|
|
1368
|
+
artifactCount: artifacts.length,
|
|
1369
|
+
artifactNames: artifacts.map((artifact) => artifact.name),
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
return (
|
|
1373
|
+
await Promise.all(
|
|
1374
|
+
artifacts.map((artifact) =>
|
|
1375
|
+
this.hydrateArtifactToPromptBlock(taskId, runId, artifact),
|
|
1376
|
+
),
|
|
1377
|
+
)
|
|
1378
|
+
).flatMap((artifactBlock) => (artifactBlock ? [artifactBlock] : []));
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
private async hydrateArtifactToPromptBlock(
|
|
1382
|
+
taskId: string,
|
|
1383
|
+
runId: string,
|
|
1384
|
+
artifact: TaskRunArtifact,
|
|
1385
|
+
): Promise<ContentBlock | null> {
|
|
1386
|
+
if (!artifact.storage_path) {
|
|
1387
|
+
this.logger.warn("Skipping artifact without storage path", {
|
|
1388
|
+
taskId,
|
|
1389
|
+
runId,
|
|
1390
|
+
artifactName: artifact.name,
|
|
1391
|
+
});
|
|
1392
|
+
return null;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
const data = await this.posthogAPI.downloadArtifact(
|
|
1396
|
+
taskId,
|
|
1397
|
+
runId,
|
|
1398
|
+
artifact.storage_path,
|
|
1399
|
+
);
|
|
1400
|
+
if (!data) {
|
|
1401
|
+
throw new Error(`Failed to download artifact ${artifact.name}`);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
const safeName = this.getSafeArtifactName(artifact.name);
|
|
1405
|
+
const artifactDir = join(
|
|
1406
|
+
this.config.repositoryPath ?? "/tmp/workspace",
|
|
1407
|
+
".posthog",
|
|
1408
|
+
"attachments",
|
|
1409
|
+
runId,
|
|
1410
|
+
artifact.id ?? safeName,
|
|
1411
|
+
);
|
|
1412
|
+
await mkdir(artifactDir, { recursive: true });
|
|
1413
|
+
|
|
1414
|
+
const artifactPath = join(artifactDir, safeName);
|
|
1415
|
+
await writeFile(artifactPath, Buffer.from(data));
|
|
1416
|
+
|
|
1417
|
+
return resourceLink(pathToFileURL(artifactPath).toString(), artifact.name, {
|
|
1418
|
+
...(artifact.content_type ? { mimeType: artifact.content_type } : {}),
|
|
1419
|
+
...(typeof artifact.size === "number" ? { size: artifact.size } : {}),
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
private getSafeArtifactName(name: string): string {
|
|
1424
|
+
const baseName = basename(name).trim();
|
|
1425
|
+
const normalizedName = baseName.replace(/[^\w.-]/g, "_");
|
|
1426
|
+
return normalizedName.length > 0 ? normalizedName : "attachment";
|
|
1231
1427
|
}
|
|
1232
1428
|
|
|
1233
1429
|
private getResumeRunId(taskRun: TaskRun | null): string | null {
|
package/src/server/bin.ts
CHANGED
|
@@ -30,7 +30,7 @@ const envSchema = z.object({
|
|
|
30
30
|
POSTHOG_CODE_RUNTIME_ADAPTER: z.enum(["claude", "codex"]).optional(),
|
|
31
31
|
POSTHOG_CODE_MODEL: z.string().optional(),
|
|
32
32
|
POSTHOG_CODE_REASONING_EFFORT: z
|
|
33
|
-
.enum(["low", "medium", "high", "max"])
|
|
33
|
+
.enum(["low", "medium", "high", "xhigh", "max"])
|
|
34
34
|
.optional(),
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -125,6 +125,16 @@ describe("validateCommandParams", () => {
|
|
|
125
125
|
expect(result.success).toBe(true);
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
+
it("accepts artifact-only user_message payloads", () => {
|
|
129
|
+
const result = validateCommandParams("user_message", {
|
|
130
|
+
artifacts: [
|
|
131
|
+
{ id: "artifact-1", storage_path: "tasks/artifacts/file.pdf" },
|
|
132
|
+
],
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(result.success).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
|
|
128
138
|
it("rejects empty content array", () => {
|
|
129
139
|
const result = validateCommandParams("user_message", {
|
|
130
140
|
content: [],
|
package/src/server/schemas.ts
CHANGED
|
@@ -41,12 +41,31 @@ export const jsonRpcRequestSchema = z.object({
|
|
|
41
41
|
|
|
42
42
|
export type JsonRpcRequest = z.infer<typeof jsonRpcRequestSchema>;
|
|
43
43
|
|
|
44
|
-
export const userMessageParamsSchema = z
|
|
45
|
-
|
|
46
|
-
z
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
export const userMessageParamsSchema = z
|
|
45
|
+
.object({
|
|
46
|
+
content: z
|
|
47
|
+
.union([
|
|
48
|
+
z.string().min(1, "Content is required"),
|
|
49
|
+
z
|
|
50
|
+
.array(z.record(z.string(), z.unknown()))
|
|
51
|
+
.min(1, "Content is required"),
|
|
52
|
+
])
|
|
53
|
+
.optional(),
|
|
54
|
+
artifacts: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
55
|
+
})
|
|
56
|
+
.refine(
|
|
57
|
+
(params) => {
|
|
58
|
+
const hasContent =
|
|
59
|
+
typeof params.content === "string"
|
|
60
|
+
? params.content.trim().length > 0
|
|
61
|
+
: Array.isArray(params.content) && params.content.length > 0;
|
|
62
|
+
const hasArtifacts =
|
|
63
|
+
Array.isArray(params.artifacts) && params.artifacts.length > 0;
|
|
64
|
+
|
|
65
|
+
return hasContent || hasArtifacts;
|
|
66
|
+
},
|
|
67
|
+
{ error: "Either content or artifacts are required" },
|
|
68
|
+
);
|
|
50
69
|
|
|
51
70
|
export const permissionResponseParamsSchema = z.object({
|
|
52
71
|
requestId: z.string().min(1, "requestId is required"),
|
package/src/server/types.ts
CHANGED
|
@@ -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
|
+
onUpdateTaskRun?: (body: unknown) => void;
|
|
8
9
|
getTask?: () => unknown;
|
|
9
10
|
getTaskRun?: () => unknown;
|
|
10
11
|
appendLogResponse?: () => AnyHttpResponse;
|
|
@@ -14,6 +15,7 @@ export function createPostHogHandlers(options: PostHogHandlersOptions = {}) {
|
|
|
14
15
|
const {
|
|
15
16
|
baseUrl = "http://localhost:8000",
|
|
16
17
|
onAppendLog,
|
|
18
|
+
onUpdateTaskRun,
|
|
17
19
|
getTask,
|
|
18
20
|
getTaskRun,
|
|
19
21
|
appendLogResponse,
|
|
@@ -85,7 +87,8 @@ export function createPostHogHandlers(options: PostHogHandlersOptions = {}) {
|
|
|
85
87
|
// PATCH /runs/:runId - Update task run
|
|
86
88
|
http.patch(
|
|
87
89
|
`${baseUrl}/api/projects/:projectId/tasks/:taskId/runs/:runId/`,
|
|
88
|
-
() => {
|
|
90
|
+
async ({ request }) => {
|
|
91
|
+
onUpdateTaskRun?.(await request.json());
|
|
89
92
|
return HttpResponse.json({});
|
|
90
93
|
},
|
|
91
94
|
),
|
package/src/types.ts
CHANGED
|
@@ -56,11 +56,14 @@ export type ArtifactType =
|
|
|
56
56
|
| "reference"
|
|
57
57
|
| "output"
|
|
58
58
|
| "artifact"
|
|
59
|
-
| "tree_snapshot"
|
|
59
|
+
| "tree_snapshot"
|
|
60
|
+
| "user_attachment";
|
|
60
61
|
|
|
61
62
|
export interface TaskRunArtifact {
|
|
63
|
+
id?: string;
|
|
62
64
|
name: string;
|
|
63
65
|
type: ArtifactType;
|
|
66
|
+
source?: string;
|
|
64
67
|
size?: number;
|
|
65
68
|
content_type?: string;
|
|
66
69
|
storage_path?: string;
|
|
@@ -152,6 +155,12 @@ export interface AgentConfig {
|
|
|
152
155
|
skipLogPersistence?: boolean;
|
|
153
156
|
/** Local cache path for instant log loading (e.g., ~/.posthog-code) */
|
|
154
157
|
localCachePath?: string;
|
|
158
|
+
/**
|
|
159
|
+
* Annotate files the agent reads with PostHog enrichment (event volume,
|
|
160
|
+
* flag rollout/staleness, experiment links). Defaults to enabled when
|
|
161
|
+
* `posthog` config is present; set `{ enabled: false }` to opt out.
|
|
162
|
+
*/
|
|
163
|
+
enricher?: { enabled?: boolean };
|
|
155
164
|
debug?: boolean;
|
|
156
165
|
onLog?: OnLogCallback;
|
|
157
166
|
}
|