@posthog/agent 2.3.256 → 2.3.261
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 +24 -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 +11 -0
- package/dist/server/agent-server.js +139 -30
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +148 -31
- package/dist/server/bin.cjs.map +1 -1
- package/dist/types.d.ts +2 -0
- package/package.json +4 -3
- package/src/adapters/acp-connection.ts +6 -1
- package/src/adapters/claude/claude-agent.ts +19 -0
- package/src/adapters/claude/session/options.ts +3 -0
- package/src/adapters/claude/types.ts +1 -0
- package/src/agent.ts +1 -0
- package/src/posthog-api.ts +14 -0
- package/src/server/agent-server.test.ts +140 -7
- package/src/server/agent-server.ts +170 -37
- package/src/server/bin.ts +13 -0
- package/src/server/question-relay.test.ts +34 -5
- package/src/server/types.ts +1 -0
- package/src/types.ts +2 -0
package/dist/types.d.ts
CHANGED
|
@@ -84,6 +84,8 @@ interface TaskExecutionOptions {
|
|
|
84
84
|
codexBinaryPath?: string;
|
|
85
85
|
instructions?: string;
|
|
86
86
|
processCallbacks?: ProcessSpawnedCallback;
|
|
87
|
+
/** Callback invoked when the agent calls the create_output tool for structured output */
|
|
88
|
+
onStructuredOutput?: (output: Record<string, unknown>) => Promise<void>;
|
|
87
89
|
}
|
|
88
90
|
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
89
91
|
type OnLogCallback = (level: LogLevel, scope: string, message: string, data?: unknown) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/agent",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.261",
|
|
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": {
|
|
@@ -82,11 +82,12 @@
|
|
|
82
82
|
"tsx": "^4.20.6",
|
|
83
83
|
"typescript": "^5.5.0",
|
|
84
84
|
"vitest": "^2.1.8",
|
|
85
|
-
"@posthog/
|
|
86
|
-
"@posthog/
|
|
85
|
+
"@posthog/git": "1.0.0",
|
|
86
|
+
"@posthog/shared": "1.0.0"
|
|
87
87
|
},
|
|
88
88
|
"dependencies": {
|
|
89
89
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
90
|
+
"ajv": "^8.17.1",
|
|
90
91
|
"@anthropic-ai/claude-agent-sdk": "0.2.76",
|
|
91
92
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
92
93
|
"@hono/node-server": "^1.19.9",
|
|
@@ -24,6 +24,8 @@ export type AcpConnectionConfig = {
|
|
|
24
24
|
processCallbacks?: ProcessSpawnedCallback;
|
|
25
25
|
codexOptions?: CodexProcessOptions;
|
|
26
26
|
allowedModelIds?: Set<string>;
|
|
27
|
+
/** Callback invoked when the agent calls the create_output tool for structured output */
|
|
28
|
+
onStructuredOutput?: (output: Record<string, unknown>) => Promise<void>;
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
export type AcpConnection = {
|
|
@@ -97,7 +99,10 @@ function createClaudeConnection(config: AcpConnectionConfig): AcpConnection {
|
|
|
97
99
|
|
|
98
100
|
let agent: ClaudeAcpAgent | null = null;
|
|
99
101
|
const agentConnection = new AgentSideConnection((client) => {
|
|
100
|
-
agent = new ClaudeAcpAgent(client,
|
|
102
|
+
agent = new ClaudeAcpAgent(client, {
|
|
103
|
+
...config.processCallbacks,
|
|
104
|
+
onStructuredOutput: config.onStructuredOutput,
|
|
105
|
+
});
|
|
101
106
|
logger.info(`Created ${agent.adapterName} agent`);
|
|
102
107
|
return agent;
|
|
103
108
|
}, agentStream);
|
|
@@ -109,6 +109,7 @@ export interface ClaudeAcpAgentOptions {
|
|
|
109
109
|
onProcessSpawned?: (info: ProcessSpawnedInfo) => void;
|
|
110
110
|
onProcessExited?: (pid: number) => void;
|
|
111
111
|
onMcpServersReady?: (serverNames: string[]) => void;
|
|
112
|
+
onStructuredOutput?: (output: Record<string, unknown>) => Promise<void>;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
@@ -483,6 +484,17 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
483
484
|
const result = handleResultMessage(message);
|
|
484
485
|
if (result.error) throw result.error;
|
|
485
486
|
|
|
487
|
+
// Deliver structured output from SDK's native outputFormat
|
|
488
|
+
if (
|
|
489
|
+
message.subtype === "success" &&
|
|
490
|
+
message.structured_output != null &&
|
|
491
|
+
this.options?.onStructuredOutput
|
|
492
|
+
) {
|
|
493
|
+
await this.options.onStructuredOutput(
|
|
494
|
+
message.structured_output as Record<string, unknown>,
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
486
498
|
// For local-only commands, forward the result text to the client
|
|
487
499
|
if (
|
|
488
500
|
isLocalOnlyCommand &&
|
|
@@ -825,6 +837,12 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
825
837
|
: {};
|
|
826
838
|
const systemPrompt = buildSystemPrompt(meta?.systemPrompt);
|
|
827
839
|
|
|
840
|
+
// Configure structured output via SDK's native outputFormat
|
|
841
|
+
const outputFormat =
|
|
842
|
+
meta?.jsonSchema && this.options?.onStructuredOutput
|
|
843
|
+
? { type: "json_schema" as const, schema: meta.jsonSchema }
|
|
844
|
+
: undefined;
|
|
845
|
+
|
|
828
846
|
this.logger.info(isResume ? "Resuming session" : "Creating new session", {
|
|
829
847
|
sessionId,
|
|
830
848
|
taskId,
|
|
@@ -854,6 +872,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
854
872
|
...(meta?.additionalRoots ?? []),
|
|
855
873
|
],
|
|
856
874
|
disableBuiltInTools: meta?.disableBuiltInTools,
|
|
875
|
+
outputFormat,
|
|
857
876
|
settingsManager,
|
|
858
877
|
onModeChange: this.createOnModeChange(),
|
|
859
878
|
onProcessSpawned: this.options?.onProcessSpawned,
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
CanUseTool,
|
|
7
7
|
McpServerConfig,
|
|
8
8
|
Options,
|
|
9
|
+
OutputFormat,
|
|
9
10
|
SpawnedProcess,
|
|
10
11
|
SpawnOptions,
|
|
11
12
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -42,6 +43,7 @@ export interface BuildOptionsParams {
|
|
|
42
43
|
forkSession?: boolean;
|
|
43
44
|
additionalDirectories?: string[];
|
|
44
45
|
disableBuiltInTools?: boolean;
|
|
46
|
+
outputFormat?: OutputFormat;
|
|
45
47
|
settingsManager: SettingsManager;
|
|
46
48
|
onModeChange?: OnModeChange;
|
|
47
49
|
onProcessSpawned?: (info: ProcessSpawnedInfo) => void;
|
|
@@ -268,6 +270,7 @@ export function buildSessionOptions(params: BuildOptionsParams): Options {
|
|
|
268
270
|
params.settingsManager,
|
|
269
271
|
params.logger,
|
|
270
272
|
),
|
|
273
|
+
outputFormat: params.outputFormat,
|
|
271
274
|
abortController: getAbortController(
|
|
272
275
|
params.userProvidedOptions?.abortController,
|
|
273
276
|
),
|
package/src/agent.ts
CHANGED
|
@@ -122,6 +122,7 @@ export class Agent {
|
|
|
122
122
|
deviceType: "local",
|
|
123
123
|
logger: this.logger,
|
|
124
124
|
processCallbacks: options.processCallbacks,
|
|
125
|
+
onStructuredOutput: options.onStructuredOutput,
|
|
125
126
|
allowedModelIds,
|
|
126
127
|
codexOptions:
|
|
127
128
|
options.adapter === "codex" && gatewayConfig
|
package/src/posthog-api.ts
CHANGED
|
@@ -158,6 +158,20 @@ export class PostHogAPIClient {
|
|
|
158
158
|
);
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
async setTaskRunOutput(
|
|
162
|
+
taskId: string,
|
|
163
|
+
runId: string,
|
|
164
|
+
output: Record<string, unknown>,
|
|
165
|
+
): Promise<TaskRun> {
|
|
166
|
+
return this.apiRequest(
|
|
167
|
+
`/api/projects/${this.getTeamId()}/tasks/${taskId}/runs/${runId}/set_output/`,
|
|
168
|
+
{
|
|
169
|
+
method: "PATCH",
|
|
170
|
+
body: JSON.stringify(output),
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
161
175
|
async appendTaskRunLog(
|
|
162
176
|
taskId: string,
|
|
163
177
|
runId: string,
|
|
@@ -20,6 +20,7 @@ interface TestableServer {
|
|
|
20
20
|
detectAndAttachPrUrl(payload: unknown, update: unknown): void;
|
|
21
21
|
detectedPrUrl: string | null;
|
|
22
22
|
buildCloudSystemPrompt(prUrl?: string | null): string;
|
|
23
|
+
buildDetectedPrContext(prUrl: string): string;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
// The Claude Agent SDK has an internal readMessages() loop that rejects with
|
|
@@ -380,14 +381,17 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
380
381
|
});
|
|
381
382
|
|
|
382
383
|
describe("buildCloudSystemPrompt", () => {
|
|
383
|
-
it("returns
|
|
384
|
+
it("returns review-first prompt for existing PRs on non-Slack runs", () => {
|
|
384
385
|
const s = createServer();
|
|
385
386
|
const prompt = (s as unknown as TestableServer).buildCloudSystemPrompt(
|
|
386
387
|
"https://github.com/org/repo/pull/1",
|
|
387
388
|
);
|
|
388
|
-
expect(prompt).toContain("
|
|
389
|
+
expect(prompt).toContain("stop with local changes ready for review");
|
|
389
390
|
expect(prompt).toContain("https://github.com/org/repo/pull/1");
|
|
390
|
-
expect(prompt).toContain(
|
|
391
|
+
expect(prompt).toContain(
|
|
392
|
+
"Do NOT create new commits, push to the branch, or update the pull request unless the user explicitly asks.",
|
|
393
|
+
);
|
|
394
|
+
expect(prompt).not.toContain("gh pr checkout");
|
|
391
395
|
expect(prompt).not.toContain("Create a draft pull request");
|
|
392
396
|
expect(prompt).toContain("Generated-By: PostHog Code");
|
|
393
397
|
expect(prompt).toContain("Task-Id: test-task-id");
|
|
@@ -396,12 +400,13 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
396
400
|
it("returns default prompt when no prUrl", () => {
|
|
397
401
|
const s = createServer();
|
|
398
402
|
const prompt = (s as unknown as TestableServer).buildCloudSystemPrompt();
|
|
399
|
-
expect(prompt).toContain("
|
|
400
|
-
expect(prompt).toContain(
|
|
401
|
-
|
|
403
|
+
expect(prompt).toContain("stop with local changes ready for review");
|
|
404
|
+
expect(prompt).toContain(
|
|
405
|
+
"Do NOT create a branch, commit, push, or open a pull request unless the user explicitly asks.",
|
|
406
|
+
);
|
|
402
407
|
expect(prompt).toContain("Generated-By: PostHog Code");
|
|
403
408
|
expect(prompt).toContain("Task-Id: test-task-id");
|
|
404
|
-
expect(prompt).toContain("
|
|
409
|
+
expect(prompt).not.toContain("gh pr create --draft");
|
|
405
410
|
});
|
|
406
411
|
|
|
407
412
|
it("returns default prompt when prUrl is null", () => {
|
|
@@ -409,12 +414,41 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
409
414
|
const prompt = (s as unknown as TestableServer).buildCloudSystemPrompt(
|
|
410
415
|
null,
|
|
411
416
|
);
|
|
417
|
+
expect(prompt).toContain("stop with local changes ready for review");
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it("returns auto-PR prompt for Slack-origin runs", () => {
|
|
421
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
422
|
+
const s = createServer();
|
|
423
|
+
const prompt = (s as unknown as TestableServer).buildCloudSystemPrompt();
|
|
412
424
|
expect(prompt).toContain("posthog-code/");
|
|
413
425
|
expect(prompt).toContain("Create a draft pull request");
|
|
414
426
|
expect(prompt).toContain("gh pr create --draft");
|
|
427
|
+
expect(prompt).toContain("Generated-By: PostHog Code");
|
|
428
|
+
expect(prompt).toContain("Task-Id: test-task-id");
|
|
429
|
+
expect(prompt).toContain("Created with [PostHog Code]");
|
|
430
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it("returns PR-update prompt for existing PRs on Slack-origin runs", () => {
|
|
434
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
435
|
+
const s = createServer();
|
|
436
|
+
const prompt = (s as unknown as TestableServer).buildCloudSystemPrompt(
|
|
437
|
+
"https://github.com/org/repo/pull/1",
|
|
438
|
+
);
|
|
439
|
+
expect(prompt).toContain(
|
|
440
|
+
"gh pr checkout https://github.com/org/repo/pull/1",
|
|
441
|
+
);
|
|
442
|
+
expect(prompt).toContain(
|
|
443
|
+
"Stage and commit all changes with a clear commit message",
|
|
444
|
+
);
|
|
445
|
+
expect(prompt).toContain("Push to the existing PR branch");
|
|
446
|
+
expect(prompt).not.toContain("Create a draft pull request");
|
|
447
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
415
448
|
});
|
|
416
449
|
|
|
417
450
|
it("includes --base flag when baseBranch is configured", () => {
|
|
451
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
418
452
|
server = new AgentServer({
|
|
419
453
|
port,
|
|
420
454
|
jwtPublicKey: TEST_PUBLIC_KEY,
|
|
@@ -433,13 +467,112 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
433
467
|
expect(prompt).toContain(
|
|
434
468
|
"gh pr create --draft --base add-yolo-to-readme",
|
|
435
469
|
);
|
|
470
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
436
471
|
});
|
|
437
472
|
|
|
438
473
|
it("omits --base flag when baseBranch is not configured", () => {
|
|
474
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
439
475
|
const s = createServer();
|
|
440
476
|
const prompt = (s as unknown as TestableServer).buildCloudSystemPrompt();
|
|
441
477
|
expect(prompt).toContain("gh pr create --draft`");
|
|
442
478
|
expect(prompt).not.toContain("--base");
|
|
479
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it("disables auto-publish for Slack-origin runs when createPr is false", () => {
|
|
483
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
484
|
+
server = new AgentServer({
|
|
485
|
+
port,
|
|
486
|
+
jwtPublicKey: TEST_PUBLIC_KEY,
|
|
487
|
+
repositoryPath: repo.path,
|
|
488
|
+
apiUrl: "http://localhost:8000",
|
|
489
|
+
apiKey: "test-api-key",
|
|
490
|
+
projectId: 1,
|
|
491
|
+
mode: "interactive",
|
|
492
|
+
taskId: "test-task-id",
|
|
493
|
+
runId: "test-run-id",
|
|
494
|
+
createPr: false,
|
|
495
|
+
});
|
|
496
|
+
const prompt = (
|
|
497
|
+
server as unknown as TestableServer
|
|
498
|
+
).buildCloudSystemPrompt();
|
|
499
|
+
expect(prompt).toContain("stop with local changes ready for review");
|
|
500
|
+
expect(prompt).not.toContain("gh pr create --draft");
|
|
501
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it("disables auto-publish for existing PRs when createPr is false", () => {
|
|
505
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
506
|
+
server = new AgentServer({
|
|
507
|
+
port,
|
|
508
|
+
jwtPublicKey: TEST_PUBLIC_KEY,
|
|
509
|
+
repositoryPath: repo.path,
|
|
510
|
+
apiUrl: "http://localhost:8000",
|
|
511
|
+
apiKey: "test-api-key",
|
|
512
|
+
projectId: 1,
|
|
513
|
+
mode: "interactive",
|
|
514
|
+
taskId: "test-task-id",
|
|
515
|
+
runId: "test-run-id",
|
|
516
|
+
createPr: false,
|
|
517
|
+
});
|
|
518
|
+
const prompt = (
|
|
519
|
+
server as unknown as TestableServer
|
|
520
|
+
).buildCloudSystemPrompt("https://github.com/org/repo/pull/1");
|
|
521
|
+
expect(prompt).toContain("stop with local changes ready for review");
|
|
522
|
+
expect(prompt).not.toContain("gh pr checkout");
|
|
523
|
+
expect(prompt).not.toContain("Push to the existing PR branch");
|
|
524
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
describe("buildDetectedPrContext", () => {
|
|
529
|
+
const prUrl = "https://github.com/org/repo/pull/1";
|
|
530
|
+
|
|
531
|
+
it("returns review-first PR context for non-Slack runs", () => {
|
|
532
|
+
const s = createServer();
|
|
533
|
+
const context = (s as unknown as TestableServer).buildDetectedPrContext(
|
|
534
|
+
prUrl,
|
|
535
|
+
);
|
|
536
|
+
expect(context).toContain("stop with local changes ready for review");
|
|
537
|
+
expect(context).toContain(
|
|
538
|
+
"Do NOT create commits, push to the PR branch, update the pull request",
|
|
539
|
+
);
|
|
540
|
+
expect(context).not.toContain("gh pr checkout");
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it("returns auto-update PR context for Slack-origin runs", () => {
|
|
544
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
545
|
+
const s = createServer();
|
|
546
|
+
const context = (s as unknown as TestableServer).buildDetectedPrContext(
|
|
547
|
+
prUrl,
|
|
548
|
+
);
|
|
549
|
+
expect(context).toContain(`gh pr checkout ${prUrl}`);
|
|
550
|
+
expect(context).toContain(
|
|
551
|
+
"Make changes, commit, and push to that branch",
|
|
552
|
+
);
|
|
553
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it("returns review-first PR context when createPr is false", () => {
|
|
557
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
558
|
+
server = new AgentServer({
|
|
559
|
+
port,
|
|
560
|
+
jwtPublicKey: TEST_PUBLIC_KEY,
|
|
561
|
+
repositoryPath: repo.path,
|
|
562
|
+
apiUrl: "http://localhost:8000",
|
|
563
|
+
apiKey: "test-api-key",
|
|
564
|
+
projectId: 1,
|
|
565
|
+
mode: "interactive",
|
|
566
|
+
taskId: "test-task-id",
|
|
567
|
+
runId: "test-run-id",
|
|
568
|
+
createPr: false,
|
|
569
|
+
});
|
|
570
|
+
const context = (
|
|
571
|
+
server as unknown as TestableServer
|
|
572
|
+
).buildDetectedPrContext(prUrl);
|
|
573
|
+
expect(context).toContain("stop with local changes ready for review");
|
|
574
|
+
expect(context).not.toContain("gh pr checkout");
|
|
575
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
443
576
|
});
|
|
444
577
|
});
|
|
445
578
|
});
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ContentBlock,
|
|
3
|
+
RequestPermissionRequest,
|
|
4
|
+
RequestPermissionResponse,
|
|
5
|
+
} from "@agentclientprotocol/sdk";
|
|
2
6
|
import {
|
|
3
7
|
ClientSideConnection,
|
|
4
8
|
ndJsonStream,
|
|
@@ -511,13 +515,9 @@ export class AgentServer {
|
|
|
511
515
|
prompt,
|
|
512
516
|
...(this.detectedPrUrl && {
|
|
513
517
|
_meta: {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
`You MUST:\n` +
|
|
518
|
-
`1. Check out the existing PR branch with \`gh pr checkout ${this.detectedPrUrl}\`\n` +
|
|
519
|
-
`2. Make changes, commit, and push to that branch\n` +
|
|
520
|
-
`You MUST NOT create a new branch, close the existing PR, or create a new PR.`,
|
|
518
|
+
// Keep the live-session PR override aligned with the startup
|
|
519
|
+
// prompt policy so non-Slack runs remain review-first.
|
|
520
|
+
prContext: this.buildDetectedPrContext(this.detectedPrUrl),
|
|
521
521
|
},
|
|
522
522
|
}),
|
|
523
523
|
});
|
|
@@ -665,6 +665,15 @@ export class AgentServer {
|
|
|
665
665
|
taskId: payload.task_id,
|
|
666
666
|
deviceType: deviceInfo.type,
|
|
667
667
|
logWriter,
|
|
668
|
+
onStructuredOutput: async (output) => {
|
|
669
|
+
await this.posthogAPI.setTaskRunOutput(
|
|
670
|
+
payload.task_id,
|
|
671
|
+
payload.run_id,
|
|
672
|
+
{
|
|
673
|
+
output,
|
|
674
|
+
},
|
|
675
|
+
);
|
|
676
|
+
},
|
|
668
677
|
});
|
|
669
678
|
|
|
670
679
|
// Tap both streams to broadcast all ACP messages via SSE (mimics local transport)
|
|
@@ -700,18 +709,25 @@ export class AgentServer {
|
|
|
700
709
|
clientCapabilities: {},
|
|
701
710
|
});
|
|
702
711
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
712
|
+
const [preTaskRun, preTask] = await Promise.all([
|
|
713
|
+
this.posthogAPI
|
|
714
|
+
.getTaskRun(payload.task_id, payload.run_id)
|
|
715
|
+
.catch((err) => {
|
|
716
|
+
this.logger.warn("Failed to fetch task run for session context", {
|
|
717
|
+
taskId: payload.task_id,
|
|
718
|
+
runId: payload.run_id,
|
|
719
|
+
error: err,
|
|
720
|
+
});
|
|
721
|
+
return null;
|
|
722
|
+
}),
|
|
723
|
+
this.posthogAPI.getTask(payload.task_id).catch((err) => {
|
|
724
|
+
this.logger.warn("Failed to fetch task for session context", {
|
|
725
|
+
taskId: payload.task_id,
|
|
726
|
+
error: err,
|
|
727
|
+
});
|
|
728
|
+
return null;
|
|
729
|
+
}),
|
|
730
|
+
]);
|
|
715
731
|
|
|
716
732
|
const prUrl =
|
|
717
733
|
typeof (preTaskRun?.state as Record<string, unknown>)
|
|
@@ -732,6 +748,7 @@ export class AgentServer {
|
|
|
732
748
|
taskRunId: payload.run_id,
|
|
733
749
|
systemPrompt: this.buildSessionSystemPrompt(prUrl),
|
|
734
750
|
allowedDomains: this.config.allowedDomains,
|
|
751
|
+
jsonSchema: preTask?.json_schema ?? null,
|
|
735
752
|
...(this.config.claudeCode?.plugins?.length && {
|
|
736
753
|
claudeCode: {
|
|
737
754
|
options: {
|
|
@@ -1104,13 +1121,53 @@ export class AgentServer {
|
|
|
1104
1121
|
return { append: cloudAppend };
|
|
1105
1122
|
}
|
|
1106
1123
|
|
|
1124
|
+
private getCloudInteractionOrigin(): string | undefined {
|
|
1125
|
+
return (
|
|
1126
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN ??
|
|
1127
|
+
process.env.CODE_INTERACTION_ORIGIN ??
|
|
1128
|
+
process.env.TWIG_INTERACTION_ORIGIN
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* Slack-origin cloud runs auto-publish by default. Every other origin is
|
|
1134
|
+
* review-first unless the user explicitly asks, and createPr=false always
|
|
1135
|
+
* disables publishing.
|
|
1136
|
+
*/
|
|
1137
|
+
private shouldAutoPublishCloudChanges(): boolean {
|
|
1138
|
+
return (
|
|
1139
|
+
this.getCloudInteractionOrigin() === "slack" &&
|
|
1140
|
+
this.config.createPr !== false
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
private buildDetectedPrContext(prUrl: string): string {
|
|
1145
|
+
if (!this.shouldAutoPublishCloudChanges()) {
|
|
1146
|
+
return (
|
|
1147
|
+
`An open pull request already exists: ${prUrl}\n` +
|
|
1148
|
+
`Use that PR as context if it is helpful, but stop with local changes ready for review.\n` +
|
|
1149
|
+
`Do NOT create commits, push to the PR branch, update the pull request, create a new branch, or create a new pull request unless the user explicitly asks.`
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
return (
|
|
1154
|
+
`IMPORTANT — OVERRIDE PREVIOUS INSTRUCTIONS ABOUT CREATING BRANCHES/PRs.\n` +
|
|
1155
|
+
`You already have an open pull request: ${prUrl}\n` +
|
|
1156
|
+
`You MUST:\n` +
|
|
1157
|
+
`1. Check out the existing PR branch with \`gh pr checkout ${prUrl}\`\n` +
|
|
1158
|
+
`2. Make changes, commit, and push to that branch\n` +
|
|
1159
|
+
`You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1107
1163
|
private buildCloudSystemPrompt(prUrl?: string | null): string {
|
|
1108
1164
|
const taskId = this.config.taskId;
|
|
1165
|
+
const shouldAutoCreatePr = this.shouldAutoPublishCloudChanges();
|
|
1109
1166
|
const attributionInstructions = `
|
|
1110
1167
|
## Attribution
|
|
1111
1168
|
Do NOT use Claude Code's default attribution (no "Co-Authored-By" trailers, no "Generated with [Claude Code]" lines).
|
|
1112
1169
|
|
|
1113
|
-
|
|
1170
|
+
If you create a commit, add the following trailers to the commit message (after a blank line at the end):
|
|
1114
1171
|
Generated-By: PostHog Code
|
|
1115
1172
|
Task-Id: ${taskId}
|
|
1116
1173
|
|
|
@@ -1126,6 +1183,21 @@ EOF
|
|
|
1126
1183
|
\`\`\``;
|
|
1127
1184
|
|
|
1128
1185
|
if (prUrl) {
|
|
1186
|
+
if (!shouldAutoCreatePr) {
|
|
1187
|
+
return `
|
|
1188
|
+
# Cloud Task Execution
|
|
1189
|
+
|
|
1190
|
+
This task already has an open pull request: ${prUrl}
|
|
1191
|
+
|
|
1192
|
+
Do the requested work, but stop with local changes ready for review.
|
|
1193
|
+
|
|
1194
|
+
Important:
|
|
1195
|
+
- Do NOT create new commits, push to the branch, or update the pull request unless the user explicitly asks.
|
|
1196
|
+
- Do NOT create a new branch or a new pull request.
|
|
1197
|
+
${attributionInstructions}
|
|
1198
|
+
`;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1129
1201
|
return `
|
|
1130
1202
|
# Cloud Task Execution
|
|
1131
1203
|
|
|
@@ -1163,6 +1235,18 @@ Important:
|
|
|
1163
1235
|
`;
|
|
1164
1236
|
}
|
|
1165
1237
|
|
|
1238
|
+
if (!shouldAutoCreatePr) {
|
|
1239
|
+
return `
|
|
1240
|
+
# Cloud Task Execution
|
|
1241
|
+
|
|
1242
|
+
Do the requested work, but stop with local changes ready for review.
|
|
1243
|
+
|
|
1244
|
+
Important:
|
|
1245
|
+
- Do NOT create a branch, commit, push, or open a pull request unless the user explicitly asks.
|
|
1246
|
+
${attributionInstructions}
|
|
1247
|
+
`;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1166
1250
|
return `
|
|
1167
1251
|
# Cloud Task Execution
|
|
1168
1252
|
|
|
@@ -1287,19 +1371,64 @@ ${attributionInstructions}
|
|
|
1287
1371
|
});
|
|
1288
1372
|
}
|
|
1289
1373
|
|
|
1374
|
+
private buildSlackQuestionRelayResponse(
|
|
1375
|
+
payload: JwtPayload,
|
|
1376
|
+
toolMeta: Record<string, unknown> | null | undefined,
|
|
1377
|
+
): RequestPermissionResponse {
|
|
1378
|
+
this.relaySlackQuestion(payload, toolMeta);
|
|
1379
|
+
return {
|
|
1380
|
+
outcome: { outcome: "cancelled" as const },
|
|
1381
|
+
_meta: {
|
|
1382
|
+
message:
|
|
1383
|
+
"This question has been relayed to the Slack thread where this task originated. " +
|
|
1384
|
+
"The user will reply there. Do NOT re-ask the question or pick an answer yourself. " +
|
|
1385
|
+
"Simply let the user know you are waiting for their reply.",
|
|
1386
|
+
},
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
private shouldBlockPublishPermission(
|
|
1391
|
+
params: RequestPermissionRequest,
|
|
1392
|
+
): boolean {
|
|
1393
|
+
if (this.config.createPr !== false) {
|
|
1394
|
+
return false;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
const meta =
|
|
1398
|
+
params.toolCall?._meta &&
|
|
1399
|
+
typeof params.toolCall._meta === "object" &&
|
|
1400
|
+
!Array.isArray(params.toolCall._meta)
|
|
1401
|
+
? (params.toolCall._meta as Record<string, unknown>)
|
|
1402
|
+
: null;
|
|
1403
|
+
const rawInput =
|
|
1404
|
+
params.toolCall?.rawInput &&
|
|
1405
|
+
typeof params.toolCall.rawInput === "object" &&
|
|
1406
|
+
!Array.isArray(params.toolCall.rawInput)
|
|
1407
|
+
? (params.toolCall.rawInput as Record<string, unknown>)
|
|
1408
|
+
: null;
|
|
1409
|
+
const toolName = typeof meta?.toolName === "string" ? meta.toolName : null;
|
|
1410
|
+
const command =
|
|
1411
|
+
typeof rawInput?.command === "string" ? rawInput.command : null;
|
|
1412
|
+
|
|
1413
|
+
return Boolean(
|
|
1414
|
+
toolName &&
|
|
1415
|
+
(toolName === "Bash" || toolName.includes("bash")) &&
|
|
1416
|
+
command &&
|
|
1417
|
+
/\bgit\s+push\b|\bgh\s+pr\s+(create|edit|ready|merge)\b/.test(command),
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1290
1421
|
private createCloudClient(payload: JwtPayload) {
|
|
1291
1422
|
const mode = this.getEffectiveMode(payload);
|
|
1292
1423
|
const interactionOrigin =
|
|
1424
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN ??
|
|
1293
1425
|
process.env.CODE_INTERACTION_ORIGIN ??
|
|
1294
1426
|
process.env.TWIG_INTERACTION_ORIGIN;
|
|
1295
1427
|
|
|
1296
1428
|
return {
|
|
1297
|
-
requestPermission: async (
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
_meta?: Record<string, unknown> | null;
|
|
1301
|
-
};
|
|
1302
|
-
}) => {
|
|
1429
|
+
requestPermission: async (
|
|
1430
|
+
params: RequestPermissionRequest,
|
|
1431
|
+
): Promise<RequestPermissionResponse> => {
|
|
1303
1432
|
// Background mode: always auto-approve permissions
|
|
1304
1433
|
// Interactive mode: also auto-approve for now (user can monitor via SSE)
|
|
1305
1434
|
// Future: interactive mode could pause and wait for user approval via SSE
|
|
@@ -1318,19 +1447,23 @@ ${attributionInstructions}
|
|
|
1318
1447
|
if (interactionOrigin === "slack") {
|
|
1319
1448
|
const codeToolKind = params.toolCall?._meta?.codeToolKind;
|
|
1320
1449
|
if (codeToolKind === "question") {
|
|
1321
|
-
this.
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
message:
|
|
1326
|
-
"This question has been relayed to the Slack thread where this task originated. " +
|
|
1327
|
-
"The user will reply there. Do NOT re-ask the question or pick an answer yourself. " +
|
|
1328
|
-
"Simply let the user know you are waiting for their reply.",
|
|
1329
|
-
},
|
|
1330
|
-
};
|
|
1450
|
+
return this.buildSlackQuestionRelayResponse(
|
|
1451
|
+
payload,
|
|
1452
|
+
params.toolCall?._meta,
|
|
1453
|
+
);
|
|
1331
1454
|
}
|
|
1332
1455
|
}
|
|
1333
1456
|
|
|
1457
|
+
if (this.shouldBlockPublishPermission(params)) {
|
|
1458
|
+
return {
|
|
1459
|
+
outcome: { outcome: "cancelled" },
|
|
1460
|
+
_meta: {
|
|
1461
|
+
message:
|
|
1462
|
+
"This run is configured to stop before publishing. Do not push commits or create/update pull requests unless the user explicitly asks.",
|
|
1463
|
+
},
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1334
1467
|
return {
|
|
1335
1468
|
outcome: {
|
|
1336
1469
|
outcome: "selected" as const,
|