@posthog/agent 2.3.261 → 2.3.267
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/codex/models.d.ts +7 -0
- package/dist/adapters/codex/models.js +13 -0
- package/dist/adapters/codex/models.js.map +1 -0
- package/dist/adapters/reasoning-effort.d.ts +10 -0
- package/dist/adapters/reasoning-effort.js +51 -0
- package/dist/adapters/reasoning-effort.js.map +1 -0
- package/dist/agent.js +16 -2
- package/dist/agent.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/posthog-api.js +5 -1
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.d.ts +17 -0
- package/dist/server/agent-server.js +199 -28
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +335 -128
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +7 -3
- package/src/acp-extensions.ts +3 -0
- package/src/adapters/claude/permissions/permission-handlers.ts +11 -5
- package/src/adapters/codex/models.ts +16 -0
- package/src/adapters/codex/spawn.ts +5 -0
- package/src/adapters/reasoning-effort.ts +35 -0
- package/src/server/agent-server.test.ts +70 -11
- package/src/server/agent-server.ts +257 -37
- package/src/server/bin.ts +24 -0
- package/src/server/schemas.test.ts +52 -0
- package/src/server/schemas.ts +16 -0
- package/src/server/types.ts +3 -0
- package/src/test/mocks/msw-handlers.ts +24 -0
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
type InProcessAcpConnection,
|
|
19
19
|
} from "../adapters/acp-connection";
|
|
20
20
|
import { selectRecentTurns } from "../adapters/claude/session/jsonl-hydration";
|
|
21
|
+
import type { CodeExecutionMode } from "../execution-mode";
|
|
22
|
+
import { DEFAULT_CODEX_MODEL } from "../gateway-models";
|
|
21
23
|
import { PostHogAPIClient } from "../posthog-api";
|
|
22
24
|
import {
|
|
23
25
|
type ConversationTurn,
|
|
@@ -161,6 +163,24 @@ interface ActiveSession {
|
|
|
161
163
|
sseController: SseController | null;
|
|
162
164
|
deviceInfo: DeviceInfo;
|
|
163
165
|
logWriter: SessionLogWriter;
|
|
166
|
+
/** Current permission mode, tracked for relay decisions */
|
|
167
|
+
permissionMode: CodeExecutionMode;
|
|
168
|
+
/** Whether a desktop client has ever connected via SSE during this session */
|
|
169
|
+
hasDesktopConnected: boolean;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getTaskRunStateString(
|
|
173
|
+
taskRun: TaskRun | null,
|
|
174
|
+
key: string,
|
|
175
|
+
): string | null {
|
|
176
|
+
const state = taskRun?.state;
|
|
177
|
+
|
|
178
|
+
if (!state || typeof state !== "object") {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const value = (state as Record<string, unknown>)[key];
|
|
183
|
+
return typeof value === "string" ? value : null;
|
|
164
184
|
}
|
|
165
185
|
|
|
166
186
|
export class AgentServer {
|
|
@@ -180,6 +200,15 @@ export class AgentServer {
|
|
|
180
200
|
// causing a second session to be created and duplicate Slack messages to be sent.
|
|
181
201
|
private initializationPromise: Promise<void> | null = null;
|
|
182
202
|
private pendingEvents: Record<string, unknown>[] = [];
|
|
203
|
+
private pendingPermissions = new Map<
|
|
204
|
+
string,
|
|
205
|
+
{
|
|
206
|
+
resolve: (response: {
|
|
207
|
+
outcome: { outcome: "selected"; optionId: string };
|
|
208
|
+
_meta?: Record<string, unknown>;
|
|
209
|
+
}) => void;
|
|
210
|
+
}
|
|
211
|
+
>();
|
|
183
212
|
|
|
184
213
|
private detachSseController(controller: SseController): void {
|
|
185
214
|
if (this.session?.sseController === controller) {
|
|
@@ -228,10 +257,18 @@ export class AgentServer {
|
|
|
228
257
|
this.app = this.createApp();
|
|
229
258
|
}
|
|
230
259
|
|
|
260
|
+
private getRuntimeAdapter(): "claude" | "codex" {
|
|
261
|
+
return this.config.runtimeAdapter ?? "claude";
|
|
262
|
+
}
|
|
263
|
+
|
|
231
264
|
private getEffectiveMode(payload: JwtPayload): AgentMode {
|
|
232
265
|
return payload.mode ?? this.config.mode;
|
|
233
266
|
}
|
|
234
267
|
|
|
268
|
+
private getSessionPermissionMode(): CodeExecutionMode {
|
|
269
|
+
return this.session?.permissionMode ?? "default";
|
|
270
|
+
}
|
|
271
|
+
|
|
235
272
|
private createApp(): Hono {
|
|
236
273
|
const app = new Hono();
|
|
237
274
|
|
|
@@ -285,6 +322,7 @@ export class AgentServer {
|
|
|
285
322
|
await this.initializeSession(payload, sseController);
|
|
286
323
|
} else {
|
|
287
324
|
this.session.sseController = sseController;
|
|
325
|
+
this.session.hasDesktopConnected = true;
|
|
288
326
|
this.replayPendingEvents();
|
|
289
327
|
}
|
|
290
328
|
|
|
@@ -579,6 +617,51 @@ export class AgentServer {
|
|
|
579
617
|
return { closed: true };
|
|
580
618
|
}
|
|
581
619
|
|
|
620
|
+
case "posthog/set_config_option":
|
|
621
|
+
case "set_config_option": {
|
|
622
|
+
const configId = params.configId as string;
|
|
623
|
+
const value = params.value as string;
|
|
624
|
+
|
|
625
|
+
this.logger.info("Set config option requested", { configId, value });
|
|
626
|
+
|
|
627
|
+
const result =
|
|
628
|
+
await this.session.clientConnection.setSessionConfigOption({
|
|
629
|
+
sessionId: this.session.acpSessionId,
|
|
630
|
+
configId,
|
|
631
|
+
value,
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
return {
|
|
635
|
+
configOptions: result.configOptions,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
case POSTHOG_NOTIFICATIONS.PERMISSION_RESPONSE:
|
|
640
|
+
case "permission_response": {
|
|
641
|
+
const requestId = params.requestId as string;
|
|
642
|
+
const optionId = params.optionId as string;
|
|
643
|
+
const customInput = params.customInput as string | undefined;
|
|
644
|
+
const answers = params.answers as Record<string, string> | undefined;
|
|
645
|
+
|
|
646
|
+
this.logger.info("Permission response received", {
|
|
647
|
+
requestId,
|
|
648
|
+
optionId,
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
const resolved = this.resolvePermission(
|
|
652
|
+
requestId,
|
|
653
|
+
optionId,
|
|
654
|
+
customInput,
|
|
655
|
+
answers,
|
|
656
|
+
);
|
|
657
|
+
if (!resolved) {
|
|
658
|
+
throw new Error(
|
|
659
|
+
`No pending permission request found for id: ${requestId}`,
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
return { resolved: true };
|
|
663
|
+
}
|
|
664
|
+
|
|
582
665
|
default:
|
|
583
666
|
throw new Error(`Unknown method: ${method}`);
|
|
584
667
|
}
|
|
@@ -638,6 +721,39 @@ export class AgentServer {
|
|
|
638
721
|
|
|
639
722
|
this.configureEnvironment();
|
|
640
723
|
|
|
724
|
+
const [preTaskRun, preTask] = await Promise.all([
|
|
725
|
+
this.posthogAPI
|
|
726
|
+
.getTaskRun(payload.task_id, payload.run_id)
|
|
727
|
+
.catch((err) => {
|
|
728
|
+
this.logger.warn("Failed to fetch task run for session context", {
|
|
729
|
+
taskId: payload.task_id,
|
|
730
|
+
runId: payload.run_id,
|
|
731
|
+
error: err,
|
|
732
|
+
});
|
|
733
|
+
return null;
|
|
734
|
+
}),
|
|
735
|
+
this.posthogAPI.getTask(payload.task_id).catch((err) => {
|
|
736
|
+
this.logger.warn("Failed to fetch task for session context", {
|
|
737
|
+
taskId: payload.task_id,
|
|
738
|
+
error: err,
|
|
739
|
+
});
|
|
740
|
+
return null;
|
|
741
|
+
}),
|
|
742
|
+
]);
|
|
743
|
+
|
|
744
|
+
const prUrl = getTaskRunStateString(preTaskRun, "slack_notified_pr_url");
|
|
745
|
+
|
|
746
|
+
if (prUrl) {
|
|
747
|
+
this.detectedPrUrl = prUrl;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const runtimeAdapter = this.getRuntimeAdapter();
|
|
751
|
+
const sessionSystemPrompt = this.buildSessionSystemPrompt(prUrl);
|
|
752
|
+
const codexInstructions =
|
|
753
|
+
runtimeAdapter === "codex"
|
|
754
|
+
? this.buildCodexInstructions(sessionSystemPrompt)
|
|
755
|
+
: undefined;
|
|
756
|
+
|
|
641
757
|
const posthogAPI = new PostHogAPIClient({
|
|
642
758
|
apiUrl: this.config.apiUrl,
|
|
643
759
|
projectId: this.config.projectId,
|
|
@@ -661,10 +777,23 @@ export class AgentServer {
|
|
|
661
777
|
});
|
|
662
778
|
|
|
663
779
|
const acpConnection = createAcpConnection({
|
|
780
|
+
adapter: runtimeAdapter,
|
|
664
781
|
taskRunId: payload.run_id,
|
|
665
782
|
taskId: payload.task_id,
|
|
666
783
|
deviceType: deviceInfo.type,
|
|
667
784
|
logWriter,
|
|
785
|
+
logger: this.logger,
|
|
786
|
+
codexOptions:
|
|
787
|
+
runtimeAdapter === "codex"
|
|
788
|
+
? {
|
|
789
|
+
cwd: this.config.repositoryPath ?? "/tmp/workspace",
|
|
790
|
+
apiBaseUrl: process.env.OPENAI_BASE_URL,
|
|
791
|
+
apiKey: this.config.apiKey,
|
|
792
|
+
model: this.config.model ?? DEFAULT_CODEX_MODEL,
|
|
793
|
+
reasoningEffort: this.config.reasoningEffort,
|
|
794
|
+
instructions: codexInstructions,
|
|
795
|
+
}
|
|
796
|
+
: undefined,
|
|
668
797
|
onStructuredOutput: async (output) => {
|
|
669
798
|
await this.posthogAPI.setTaskRunOutput(
|
|
670
799
|
payload.task_id,
|
|
@@ -709,50 +838,34 @@ export class AgentServer {
|
|
|
709
838
|
clientCapabilities: {},
|
|
710
839
|
});
|
|
711
840
|
|
|
712
|
-
const
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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
|
-
]);
|
|
731
|
-
|
|
732
|
-
const prUrl =
|
|
733
|
-
typeof (preTaskRun?.state as Record<string, unknown>)
|
|
734
|
-
?.slack_notified_pr_url === "string"
|
|
735
|
-
? ((preTaskRun?.state as Record<string, unknown>)
|
|
736
|
-
.slack_notified_pr_url as string)
|
|
737
|
-
: null;
|
|
738
|
-
|
|
739
|
-
if (prUrl) {
|
|
740
|
-
this.detectedPrUrl = prUrl;
|
|
741
|
-
}
|
|
742
|
-
|
|
841
|
+
const runState = preTaskRun?.state as Record<string, unknown> | undefined;
|
|
842
|
+
// Cloud runs default to bypassPermissions (auto-approve everything).
|
|
843
|
+
// Only PostHog Code sets initial_permission_mode explicitly (e.g., "plan").
|
|
844
|
+
const initialPermissionMode: CodeExecutionMode =
|
|
845
|
+
typeof runState?.initial_permission_mode === "string"
|
|
846
|
+
? (runState.initial_permission_mode as CodeExecutionMode)
|
|
847
|
+
: "bypassPermissions";
|
|
743
848
|
const sessionResponse = await clientConnection.newSession({
|
|
744
849
|
cwd: this.config.repositoryPath ?? "/tmp/workspace",
|
|
745
850
|
mcpServers: this.config.mcpServers ?? [],
|
|
746
851
|
_meta: {
|
|
747
852
|
sessionId: payload.run_id,
|
|
748
853
|
taskRunId: payload.run_id,
|
|
749
|
-
systemPrompt:
|
|
854
|
+
systemPrompt: sessionSystemPrompt,
|
|
855
|
+
...(this.config.model && { model: this.config.model }),
|
|
750
856
|
allowedDomains: this.config.allowedDomains,
|
|
751
857
|
jsonSchema: preTask?.json_schema ?? null,
|
|
858
|
+
permissionMode: initialPermissionMode,
|
|
752
859
|
...(this.config.claudeCode?.plugins?.length && {
|
|
753
860
|
claudeCode: {
|
|
754
861
|
options: {
|
|
755
|
-
|
|
862
|
+
...(this.config.claudeCode?.plugins?.length && {
|
|
863
|
+
plugins: this.config.claudeCode.plugins,
|
|
864
|
+
}),
|
|
865
|
+
...(runtimeAdapter === "claude" &&
|
|
866
|
+
this.config.reasoningEffort && {
|
|
867
|
+
effort: this.config.reasoningEffort,
|
|
868
|
+
}),
|
|
756
869
|
},
|
|
757
870
|
},
|
|
758
871
|
}),
|
|
@@ -774,6 +887,8 @@ export class AgentServer {
|
|
|
774
887
|
sseController,
|
|
775
888
|
deviceInfo,
|
|
776
889
|
logWriter,
|
|
890
|
+
permissionMode: initialPermissionMode,
|
|
891
|
+
hasDesktopConnected: sseController !== null,
|
|
777
892
|
};
|
|
778
893
|
|
|
779
894
|
this.logger = new Logger({
|
|
@@ -791,6 +906,7 @@ export class AgentServer {
|
|
|
791
906
|
this.logger.info(
|
|
792
907
|
`Agent version: ${this.config.version ?? packageJson.version}`,
|
|
793
908
|
);
|
|
909
|
+
this.logger.info(`Initial permission mode: ${initialPermissionMode}`);
|
|
794
910
|
|
|
795
911
|
// Signal in_progress so the UI can start polling for updates
|
|
796
912
|
this.posthogAPI
|
|
@@ -1121,6 +1237,14 @@ export class AgentServer {
|
|
|
1121
1237
|
return { append: cloudAppend };
|
|
1122
1238
|
}
|
|
1123
1239
|
|
|
1240
|
+
private buildCodexInstructions(
|
|
1241
|
+
systemPrompt: string | { append: string },
|
|
1242
|
+
): string {
|
|
1243
|
+
return typeof systemPrompt === "string"
|
|
1244
|
+
? systemPrompt
|
|
1245
|
+
: systemPrompt.append;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1124
1248
|
private getCloudInteractionOrigin(): string | undefined {
|
|
1125
1249
|
return (
|
|
1126
1250
|
process.env.POSTHOG_CODE_INTERACTION_ORIGIN ??
|
|
@@ -1429,12 +1553,10 @@ ${attributionInstructions}
|
|
|
1429
1553
|
requestPermission: async (
|
|
1430
1554
|
params: RequestPermissionRequest,
|
|
1431
1555
|
): Promise<RequestPermissionResponse> => {
|
|
1432
|
-
// Background mode: always auto-approve permissions
|
|
1433
|
-
// Interactive mode: also auto-approve for now (user can monitor via SSE)
|
|
1434
|
-
// Future: interactive mode could pause and wait for user approval via SSE
|
|
1435
1556
|
this.logger.debug("Permission request", {
|
|
1436
1557
|
mode,
|
|
1437
1558
|
interactionOrigin,
|
|
1559
|
+
kind: params.toolCall?.kind,
|
|
1438
1560
|
options: params.options,
|
|
1439
1561
|
});
|
|
1440
1562
|
|
|
@@ -1444,8 +1566,11 @@ ${attributionInstructions}
|
|
|
1444
1566
|
const selectedOptionId =
|
|
1445
1567
|
allowOption?.optionId ?? params.options[0].optionId;
|
|
1446
1568
|
|
|
1569
|
+
const codeToolKind = params.toolCall?._meta?.codeToolKind;
|
|
1570
|
+
const isPlanApproval = params.toolCall?.kind === "switch_mode";
|
|
1571
|
+
|
|
1572
|
+
// Relay questions to Slack when interaction originated there
|
|
1447
1573
|
if (interactionOrigin === "slack") {
|
|
1448
|
-
const codeToolKind = params.toolCall?._meta?.codeToolKind;
|
|
1449
1574
|
if (codeToolKind === "question") {
|
|
1450
1575
|
return this.buildSlackQuestionRelayResponse(
|
|
1451
1576
|
payload,
|
|
@@ -1454,6 +1579,27 @@ ${attributionInstructions}
|
|
|
1454
1579
|
}
|
|
1455
1580
|
}
|
|
1456
1581
|
|
|
1582
|
+
// Relay permission requests to the desktop app when:
|
|
1583
|
+
// - Questions: always relay (need human answers regardless of mode)
|
|
1584
|
+
// - Plan approvals: always relay
|
|
1585
|
+
// - Edit/bash in "default" mode: relay for manual approval
|
|
1586
|
+
// Other modes auto-approve. No client connected → auto-approve.
|
|
1587
|
+
{
|
|
1588
|
+
const isQuestion = codeToolKind === "question";
|
|
1589
|
+
const sessionPermissionMode = this.getSessionPermissionMode();
|
|
1590
|
+
const needsRelay =
|
|
1591
|
+
isQuestion || isPlanApproval || sessionPermissionMode === "default";
|
|
1592
|
+
|
|
1593
|
+
if (needsRelay && this.session?.hasDesktopConnected) {
|
|
1594
|
+
this.logger.info("Relaying permission to connected client", {
|
|
1595
|
+
kind: params.toolCall?.kind,
|
|
1596
|
+
isQuestion,
|
|
1597
|
+
sessionPermissionMode,
|
|
1598
|
+
});
|
|
1599
|
+
return this.relayPermissionToClient(params);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1457
1603
|
if (this.shouldBlockPublishPermission(params)) {
|
|
1458
1604
|
return {
|
|
1459
1605
|
outcome: { outcome: "cancelled" },
|
|
@@ -1481,6 +1627,19 @@ ${attributionInstructions}
|
|
|
1481
1627
|
sessionId: string;
|
|
1482
1628
|
update?: Record<string, unknown>;
|
|
1483
1629
|
}) => {
|
|
1630
|
+
// Track permission mode changes for relay decisions
|
|
1631
|
+
if (
|
|
1632
|
+
params.update?.sessionUpdate === "current_mode_update" &&
|
|
1633
|
+
typeof params.update?.currentModeId === "string" &&
|
|
1634
|
+
this.session
|
|
1635
|
+
) {
|
|
1636
|
+
this.session.permissionMode = params.update
|
|
1637
|
+
.currentModeId as CodeExecutionMode;
|
|
1638
|
+
this.logger.info("Permission mode updated", {
|
|
1639
|
+
mode: params.update.currentModeId,
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1484
1643
|
// session/update notifications flow through the tapped stream (like local transport)
|
|
1485
1644
|
// Only handle tree state capture for file changes here
|
|
1486
1645
|
if (params.update?.sessionUpdate === "tool_call_update") {
|
|
@@ -1730,6 +1889,16 @@ ${attributionInstructions}
|
|
|
1730
1889
|
this.logger.error("Failed to flush session logs", error);
|
|
1731
1890
|
}
|
|
1732
1891
|
|
|
1892
|
+
// Drain pending permissions before ACP cleanup to avoid deadlocks —
|
|
1893
|
+
// cleanup may await operations that are blocked on a permission response.
|
|
1894
|
+
for (const [, pending] of this.pendingPermissions) {
|
|
1895
|
+
pending.resolve({
|
|
1896
|
+
outcome: { outcome: "selected", optionId: "reject" },
|
|
1897
|
+
_meta: { customInput: "Session is shutting down." },
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
this.pendingPermissions.clear();
|
|
1901
|
+
|
|
1733
1902
|
try {
|
|
1734
1903
|
await this.session.acpConnection.cleanup();
|
|
1735
1904
|
} catch (error) {
|
|
@@ -1823,4 +1992,55 @@ ${attributionInstructions}
|
|
|
1823
1992
|
this.detachSseController(controller);
|
|
1824
1993
|
}
|
|
1825
1994
|
}
|
|
1995
|
+
|
|
1996
|
+
/**
|
|
1997
|
+
* Relay a permission request (e.g., plan approval) to the connected desktop
|
|
1998
|
+
* app via SSE and wait for a response via the `/command` endpoint.
|
|
1999
|
+
*
|
|
2000
|
+
* The promise waits indefinitely — if SSE is disconnected, the event is
|
|
2001
|
+
* buffered by broadcastEvent and replayed when the client reconnects. Session
|
|
2002
|
+
* cleanup force-resolves all pending permissions, so there is no leak.
|
|
2003
|
+
*/
|
|
2004
|
+
private relayPermissionToClient(params: {
|
|
2005
|
+
options: Array<{ kind: string; optionId: string; name?: string }>;
|
|
2006
|
+
toolCall?: Record<string, unknown> | null;
|
|
2007
|
+
}): Promise<{
|
|
2008
|
+
outcome: { outcome: "selected"; optionId: string };
|
|
2009
|
+
_meta?: Record<string, unknown>;
|
|
2010
|
+
}> {
|
|
2011
|
+
const requestId = crypto.randomUUID();
|
|
2012
|
+
|
|
2013
|
+
this.broadcastEvent({
|
|
2014
|
+
type: "permission_request",
|
|
2015
|
+
requestId,
|
|
2016
|
+
options: params.options,
|
|
2017
|
+
toolCall: params.toolCall,
|
|
2018
|
+
});
|
|
2019
|
+
|
|
2020
|
+
return new Promise((resolve) => {
|
|
2021
|
+
this.pendingPermissions.set(requestId, { resolve });
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
private resolvePermission(
|
|
2026
|
+
requestId: string,
|
|
2027
|
+
optionId: string,
|
|
2028
|
+
customInput?: string,
|
|
2029
|
+
answers?: Record<string, string>,
|
|
2030
|
+
): boolean {
|
|
2031
|
+
const pending = this.pendingPermissions.get(requestId);
|
|
2032
|
+
if (!pending) return false;
|
|
2033
|
+
|
|
2034
|
+
this.pendingPermissions.delete(requestId);
|
|
2035
|
+
|
|
2036
|
+
const meta: Record<string, unknown> = {};
|
|
2037
|
+
if (customInput) meta.customInput = customInput;
|
|
2038
|
+
if (answers) meta.answers = answers;
|
|
2039
|
+
|
|
2040
|
+
pending.resolve({
|
|
2041
|
+
outcome: { outcome: "selected" as const, optionId },
|
|
2042
|
+
...(Object.keys(meta).length > 0 ? { _meta: meta } : {}),
|
|
2043
|
+
});
|
|
2044
|
+
return true;
|
|
2045
|
+
}
|
|
1826
2046
|
}
|
package/src/server/bin.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { z } from "zod/v4";
|
|
4
|
+
import { isSupportedReasoningEffort } from "../adapters/reasoning-effort";
|
|
4
5
|
import { AgentServer } from "./agent-server";
|
|
5
6
|
import { claudeCodeConfigSchema, mcpServersSchema } from "./schemas";
|
|
6
7
|
|
|
@@ -26,6 +27,11 @@ const envSchema = z.object({
|
|
|
26
27
|
})
|
|
27
28
|
.regex(/^\d+$/, "POSTHOG_PROJECT_ID must be a numeric string")
|
|
28
29
|
.transform((val) => parseInt(val, 10)),
|
|
30
|
+
POSTHOG_CODE_RUNTIME_ADAPTER: z.enum(["claude", "codex"]).optional(),
|
|
31
|
+
POSTHOG_CODE_MODEL: z.string().optional(),
|
|
32
|
+
POSTHOG_CODE_REASONING_EFFORT: z
|
|
33
|
+
.enum(["low", "medium", "high", "max"])
|
|
34
|
+
.optional(),
|
|
29
35
|
});
|
|
30
36
|
|
|
31
37
|
const program = new Command();
|
|
@@ -124,6 +130,21 @@ program
|
|
|
124
130
|
.filter(Boolean)
|
|
125
131
|
: undefined;
|
|
126
132
|
|
|
133
|
+
if (
|
|
134
|
+
env.POSTHOG_CODE_RUNTIME_ADAPTER &&
|
|
135
|
+
env.POSTHOG_CODE_MODEL &&
|
|
136
|
+
env.POSTHOG_CODE_REASONING_EFFORT &&
|
|
137
|
+
!isSupportedReasoningEffort(
|
|
138
|
+
env.POSTHOG_CODE_RUNTIME_ADAPTER,
|
|
139
|
+
env.POSTHOG_CODE_MODEL,
|
|
140
|
+
env.POSTHOG_CODE_REASONING_EFFORT,
|
|
141
|
+
)
|
|
142
|
+
) {
|
|
143
|
+
program.error(
|
|
144
|
+
`POSTHOG_CODE_REASONING_EFFORT '${env.POSTHOG_CODE_REASONING_EFFORT}' is not supported for ${env.POSTHOG_CODE_RUNTIME_ADAPTER} model '${env.POSTHOG_CODE_MODEL}'.`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
127
148
|
const server = new AgentServer({
|
|
128
149
|
port: parseInt(options.port, 10),
|
|
129
150
|
jwtPublicKey: env.JWT_PUBLIC_KEY,
|
|
@@ -139,6 +160,9 @@ program
|
|
|
139
160
|
baseBranch: options.baseBranch,
|
|
140
161
|
claudeCode,
|
|
141
162
|
allowedDomains,
|
|
163
|
+
runtimeAdapter: env.POSTHOG_CODE_RUNTIME_ADAPTER,
|
|
164
|
+
model: env.POSTHOG_CODE_MODEL,
|
|
165
|
+
reasoningEffort: env.POSTHOG_CODE_REASONING_EFFORT,
|
|
142
166
|
});
|
|
143
167
|
|
|
144
168
|
process.on("SIGINT", async () => {
|
|
@@ -132,4 +132,56 @@ describe("validateCommandParams", () => {
|
|
|
132
132
|
|
|
133
133
|
expect(result.success).toBe(false);
|
|
134
134
|
});
|
|
135
|
+
|
|
136
|
+
it("accepts valid permission_response", () => {
|
|
137
|
+
const result = validateCommandParams("permission_response", {
|
|
138
|
+
requestId: "abc-123",
|
|
139
|
+
optionId: "acceptEdits",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(result.success).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("accepts permission_response with customInput", () => {
|
|
146
|
+
const result = validateCommandParams("permission_response", {
|
|
147
|
+
requestId: "abc-123",
|
|
148
|
+
optionId: "reject_with_feedback",
|
|
149
|
+
customInput: "Please change the approach",
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(result.success).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("rejects permission_response without requestId", () => {
|
|
156
|
+
const result = validateCommandParams("permission_response", {
|
|
157
|
+
optionId: "acceptEdits",
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(result.success).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("rejects permission_response without optionId", () => {
|
|
164
|
+
const result = validateCommandParams("permission_response", {
|
|
165
|
+
requestId: "abc-123",
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(result.success).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("accepts valid set_config_option", () => {
|
|
172
|
+
const result = validateCommandParams("set_config_option", {
|
|
173
|
+
configId: "mode",
|
|
174
|
+
value: "plan",
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
expect(result.success).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("rejects set_config_option without configId", () => {
|
|
181
|
+
const result = validateCommandParams("set_config_option", {
|
|
182
|
+
value: "plan",
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
expect(result.success).toBe(false);
|
|
186
|
+
});
|
|
135
187
|
});
|
package/src/server/schemas.ts
CHANGED
|
@@ -48,6 +48,18 @@ export const userMessageParamsSchema = z.object({
|
|
|
48
48
|
]),
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
export const permissionResponseParamsSchema = z.object({
|
|
52
|
+
requestId: z.string().min(1, "requestId is required"),
|
|
53
|
+
optionId: z.string().min(1, "optionId is required"),
|
|
54
|
+
customInput: z.string().optional(),
|
|
55
|
+
answers: z.record(z.string(), z.string()).optional(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const setConfigOptionParamsSchema = z.object({
|
|
59
|
+
configId: z.string().min(1, "configId is required"),
|
|
60
|
+
value: z.string().min(1, "value is required"),
|
|
61
|
+
});
|
|
62
|
+
|
|
51
63
|
export const commandParamsSchemas = {
|
|
52
64
|
user_message: userMessageParamsSchema,
|
|
53
65
|
"posthog/user_message": userMessageParamsSchema,
|
|
@@ -55,6 +67,10 @@ export const commandParamsSchemas = {
|
|
|
55
67
|
"posthog/cancel": z.object({}).optional(),
|
|
56
68
|
close: z.object({}).optional(),
|
|
57
69
|
"posthog/close": z.object({}).optional(),
|
|
70
|
+
permission_response: permissionResponseParamsSchema,
|
|
71
|
+
"posthog/permission_response": permissionResponseParamsSchema,
|
|
72
|
+
set_config_option: setConfigOptionParamsSchema,
|
|
73
|
+
"posthog/set_config_option": setConfigOptionParamsSchema,
|
|
58
74
|
} as const;
|
|
59
75
|
|
|
60
76
|
export type CommandMethod = keyof typeof commandParamsSchemas;
|
package/src/server/types.ts
CHANGED
|
@@ -20,6 +20,30 @@ export function createPostHogHandlers(options: PostHogHandlersOptions = {}) {
|
|
|
20
20
|
} = options;
|
|
21
21
|
|
|
22
22
|
return [
|
|
23
|
+
// GET local LLM gateway models - session initialization fetches these in the
|
|
24
|
+
// background for command/model metadata.
|
|
25
|
+
http.get("http://localhost:3308/:product/v1/models", () => {
|
|
26
|
+
return HttpResponse.json({
|
|
27
|
+
object: "list",
|
|
28
|
+
data: [
|
|
29
|
+
{
|
|
30
|
+
id: "claude-opus-4-6",
|
|
31
|
+
owned_by: "anthropic",
|
|
32
|
+
context_window: 200000,
|
|
33
|
+
supports_streaming: true,
|
|
34
|
+
supports_vision: true,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "gpt-5.4",
|
|
38
|
+
owned_by: "openai",
|
|
39
|
+
context_window: 200000,
|
|
40
|
+
supports_streaming: true,
|
|
41
|
+
supports_vision: true,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
}),
|
|
46
|
+
|
|
23
47
|
// POST /append_log/ - Agent log entries
|
|
24
48
|
http.post(
|
|
25
49
|
`${baseUrl}/api/projects/:projectId/tasks/:taskId/runs/:runId/append_log/`,
|