@posthog/agent 2.3.520 → 2.3.526
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/structured-output-mcp-server.d.ts +2 -0
- package/dist/adapters/codex/structured-output-mcp-server.js +54 -0
- package/dist/adapters/codex/structured-output-mcp-server.js.map +1 -0
- package/dist/agent.js +126 -20
- package/dist/agent.js.map +1 -1
- package/dist/handoff-checkpoint.js +2 -2
- package/dist/handoff-checkpoint.js.map +1 -1
- package/dist/posthog-api.js +2 -2
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.js +133 -27
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +156 -49
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +4 -4
- package/src/adapters/acp-connection.ts +1 -0
- package/src/adapters/codex/codex-agent.test.ts +134 -1
- package/src/adapters/codex/codex-agent.ts +122 -17
- package/src/adapters/codex/codex-client.test.ts +178 -0
- package/src/adapters/codex/codex-client.ts +68 -0
- package/src/adapters/codex/structured-output-constants.ts +9 -0
- package/src/adapters/codex/structured-output-mcp-server.ts +72 -0
- package/src/handoff-checkpoint.ts +2 -2
- package/src/server/agent-server.ts +5 -5
|
@@ -36,12 +36,45 @@ import {
|
|
|
36
36
|
import type { PermissionMode } from "../../execution-mode";
|
|
37
37
|
import type { Logger } from "../../utils/logger";
|
|
38
38
|
import type { CodexSessionState } from "./session-state";
|
|
39
|
+
import {
|
|
40
|
+
STRUCTURED_OUTPUT_MCP_NAME,
|
|
41
|
+
STRUCTURED_OUTPUT_TOOL_NAME,
|
|
42
|
+
} from "./structured-output-constants";
|
|
39
43
|
|
|
40
44
|
export interface CodexClientCallbacks {
|
|
41
45
|
/** Called when a usage_update session notification is received */
|
|
42
46
|
onUsageUpdate?: (update: Record<string, unknown>) => void;
|
|
43
47
|
/** When set, Read responses are annotated with PostHog enrichment before reaching codex-acp. */
|
|
44
48
|
enrichmentDeps?: FileEnrichmentDeps;
|
|
49
|
+
/**
|
|
50
|
+
* Called once per session when the agent completes the injected
|
|
51
|
+
* `create_output` MCP tool. Matches the Claude adapter's structured
|
|
52
|
+
* output delivery.
|
|
53
|
+
*/
|
|
54
|
+
onStructuredOutput?: (output: Record<string, unknown>) => Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Tool calls for our injected MCP server surface in ACP `tool_call` /
|
|
59
|
+
* `tool_call_update` notifications. The `title` from codex-acp can be
|
|
60
|
+
* either the bare tool name or prefixed (`mcp__<server>__<tool>`); match
|
|
61
|
+
* both forms but require the server name on prefixed titles so an unrelated
|
|
62
|
+
* user tool happening to contain `create_output` doesn't trigger us.
|
|
63
|
+
*/
|
|
64
|
+
function isStructuredOutputToolCall(title: string | undefined | null): boolean {
|
|
65
|
+
if (!title) return false;
|
|
66
|
+
if (title === STRUCTURED_OUTPUT_TOOL_NAME) return true;
|
|
67
|
+
return (
|
|
68
|
+
title.includes(STRUCTURED_OUTPUT_MCP_NAME) &&
|
|
69
|
+
title.includes(STRUCTURED_OUTPUT_TOOL_NAME)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function toRecord(value: unknown): Record<string, unknown> | null {
|
|
74
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
75
|
+
return value as Record<string, unknown>;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
45
78
|
}
|
|
46
79
|
|
|
47
80
|
const AUTO_APPROVED_KINDS: Record<PermissionMode, Set<ToolKind>> = {
|
|
@@ -96,6 +129,14 @@ export function createCodexClient(
|
|
|
96
129
|
callbacks?: CodexClientCallbacks,
|
|
97
130
|
): Client {
|
|
98
131
|
const terminalHandles = new Map<string, TerminalHandle>();
|
|
132
|
+
// Track rawInput across tool_call → tool_call_update → completed so we can
|
|
133
|
+
// fire onStructuredOutput exactly once per tool call id. Entries stay in
|
|
134
|
+
// the map after firing with `fired: true` so a re-emitted completion
|
|
135
|
+
// (if codex-acp ever resends one) is a no-op.
|
|
136
|
+
const structuredOutputState = new Map<
|
|
137
|
+
string,
|
|
138
|
+
{ rawInput?: Record<string, unknown>; fired: boolean }
|
|
139
|
+
>();
|
|
99
140
|
|
|
100
141
|
return {
|
|
101
142
|
async requestPermission(
|
|
@@ -125,6 +166,33 @@ export function createCodexClient(
|
|
|
125
166
|
|
|
126
167
|
async sessionUpdate(params: SessionNotification): Promise<void> {
|
|
127
168
|
const update = params.update as Record<string, unknown> | undefined;
|
|
169
|
+
|
|
170
|
+
if (
|
|
171
|
+
callbacks?.onStructuredOutput &&
|
|
172
|
+
(update?.sessionUpdate === "tool_call" ||
|
|
173
|
+
update?.sessionUpdate === "tool_call_update")
|
|
174
|
+
) {
|
|
175
|
+
const toolCallId = update.toolCallId as string | undefined;
|
|
176
|
+
const title = update.title as string | undefined;
|
|
177
|
+
if (toolCallId && isStructuredOutputToolCall(title)) {
|
|
178
|
+
const entry = structuredOutputState.get(toolCallId) ?? {
|
|
179
|
+
fired: false,
|
|
180
|
+
};
|
|
181
|
+
const rawInput = toRecord(update.rawInput);
|
|
182
|
+
if (rawInput) entry.rawInput = rawInput;
|
|
183
|
+
structuredOutputState.set(toolCallId, entry);
|
|
184
|
+
|
|
185
|
+
if (update.status === "completed" && !entry.fired && entry.rawInput) {
|
|
186
|
+
entry.fired = true;
|
|
187
|
+
try {
|
|
188
|
+
await callbacks.onStructuredOutput(entry.rawInput);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
logger.warn("onStructuredOutput callback threw", { error: err });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
128
196
|
if (update?.sessionUpdate === "usage_update") {
|
|
129
197
|
const used = update.used as number | undefined;
|
|
130
198
|
const size = update.size as number | undefined;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared identifiers for the injected structured-output MCP server.
|
|
3
|
+
* Imported by codex-agent.ts (server config), codex-client.ts (tool-call
|
|
4
|
+
* matching), and structured-output-mcp-server.ts (tool registration) so the
|
|
5
|
+
* three stay in sync.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const STRUCTURED_OUTPUT_MCP_NAME = "posthog_output";
|
|
9
|
+
export const STRUCTURED_OUTPUT_TOOL_NAME = "create_output";
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone stdio MCP server for structured output in the Codex adapter.
|
|
3
|
+
*
|
|
4
|
+
* Spawned by codex-acp as an MCP server process. Reads the JSON schema
|
|
5
|
+
* from the POSTHOG_OUTPUT_SCHEMA env var (base64-encoded) and registers
|
|
6
|
+
* a tool whose Zod shape McpServer.tool() validates on each call.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* POSTHOG_OUTPUT_SCHEMA=<base64> node structured-output-mcp-server.js
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
13
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import {
|
|
16
|
+
STRUCTURED_OUTPUT_MCP_NAME,
|
|
17
|
+
STRUCTURED_OUTPUT_TOOL_NAME,
|
|
18
|
+
} from "./structured-output-constants";
|
|
19
|
+
|
|
20
|
+
function die(message: string): never {
|
|
21
|
+
process.stderr.write(`[structured-output-mcp-server] ${message}\n`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const schemaEnv = process.env.POSTHOG_OUTPUT_SCHEMA;
|
|
26
|
+
if (!schemaEnv) {
|
|
27
|
+
die("POSTHOG_OUTPUT_SCHEMA env var is required");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let jsonSchema: Record<string, unknown>;
|
|
31
|
+
try {
|
|
32
|
+
jsonSchema = JSON.parse(Buffer.from(schemaEnv, "base64").toString("utf-8"));
|
|
33
|
+
} catch (err) {
|
|
34
|
+
die(`Failed to parse POSTHOG_OUTPUT_SCHEMA as base64-encoded JSON: ${err}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const zodType = z.fromJSONSchema(jsonSchema);
|
|
38
|
+
if (!(zodType instanceof z.ZodObject)) {
|
|
39
|
+
die(
|
|
40
|
+
`POSTHOG_OUTPUT_SCHEMA must describe a JSON object schema (got ${zodType.constructor.name})`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
// McpServer.tool() expects a mutable ZodRawShape
|
|
44
|
+
const zodShape = { ...zodType.shape } as z.ZodRawShape;
|
|
45
|
+
|
|
46
|
+
const server = new McpServer({
|
|
47
|
+
name: STRUCTURED_OUTPUT_MCP_NAME,
|
|
48
|
+
version: "1.0.0",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
server.tool(
|
|
52
|
+
STRUCTURED_OUTPUT_TOOL_NAME,
|
|
53
|
+
"Submit the structured output for this task. Call this tool with the required fields to deliver your final result.",
|
|
54
|
+
zodShape,
|
|
55
|
+
async () => {
|
|
56
|
+
// McpServer.tool() validates `args` against `zodShape` before invoking
|
|
57
|
+
// this handler, so reaching this point means the input is valid. The
|
|
58
|
+
// parent process captures the validated output by intercepting the
|
|
59
|
+
// tool call in the ACP stream.
|
|
60
|
+
return {
|
|
61
|
+
content: [
|
|
62
|
+
{
|
|
63
|
+
type: "text" as const,
|
|
64
|
+
text: "Output submitted successfully.",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const transport = new StdioServerTransport();
|
|
72
|
+
await server.connect(transport);
|
|
@@ -300,7 +300,7 @@ export class HandoffCheckpointTracker {
|
|
|
300
300
|
checkpoint: GitHandoffCheckpoint,
|
|
301
301
|
uploads: Uploads,
|
|
302
302
|
): void {
|
|
303
|
-
this.logger.
|
|
303
|
+
this.logger.debug("Captured handoff checkpoint", {
|
|
304
304
|
branch: checkpoint.branch,
|
|
305
305
|
head: checkpoint.head?.slice(0, 7),
|
|
306
306
|
totalBytes: this.sumRawBytes(uploads.pack, uploads.index),
|
|
@@ -312,7 +312,7 @@ export class HandoffCheckpointTracker {
|
|
|
312
312
|
_downloads: Downloads,
|
|
313
313
|
totalBytes: number,
|
|
314
314
|
): void {
|
|
315
|
-
this.logger.
|
|
315
|
+
this.logger.debug("Applied handoff checkpoint", {
|
|
316
316
|
branch: checkpoint.branch,
|
|
317
317
|
head: checkpoint.head?.slice(0, 7),
|
|
318
318
|
totalBytes,
|
|
@@ -588,7 +588,7 @@ export class AgentServer {
|
|
|
588
588
|
switch (method) {
|
|
589
589
|
case POSTHOG_NOTIFICATIONS.USER_MESSAGE:
|
|
590
590
|
case "user_message": {
|
|
591
|
-
this.logger.
|
|
591
|
+
this.logger.debug("Received user_message command", {
|
|
592
592
|
hasContent:
|
|
593
593
|
typeof params.content === "string"
|
|
594
594
|
? params.content.trim().length > 0
|
|
@@ -608,7 +608,7 @@ export class AgentServer {
|
|
|
608
608
|
if (prompt.length === 0) {
|
|
609
609
|
throw new Error("User message cannot be empty");
|
|
610
610
|
}
|
|
611
|
-
this.logger.
|
|
611
|
+
this.logger.debug("Built user_message prompt", {
|
|
612
612
|
blockTypes: prompt.map((block) => block.type),
|
|
613
613
|
});
|
|
614
614
|
const promptPreview = promptBlocksToText(prompt);
|
|
@@ -718,7 +718,7 @@ export class AgentServer {
|
|
|
718
718
|
? params.mcpServers
|
|
719
719
|
: [];
|
|
720
720
|
|
|
721
|
-
this.logger.
|
|
721
|
+
this.logger.debug("Refresh session requested", {
|
|
722
722
|
serverCount: mcpServers.length,
|
|
723
723
|
});
|
|
724
724
|
|
|
@@ -1191,7 +1191,7 @@ export class AgentServer {
|
|
|
1191
1191
|
this.resumeState.latestGitCheckpoint,
|
|
1192
1192
|
);
|
|
1193
1193
|
checkpointApplied = true;
|
|
1194
|
-
this.logger.
|
|
1194
|
+
this.logger.debug("Git checkpoint applied", {
|
|
1195
1195
|
branch: this.resumeState.latestGitCheckpoint.branch,
|
|
1196
1196
|
head: this.resumeState.latestGitCheckpoint.head,
|
|
1197
1197
|
packBytes: metrics.packBytes,
|
|
@@ -1314,7 +1314,7 @@ export class AgentServer {
|
|
|
1314
1314
|
taskId: taskRun.task,
|
|
1315
1315
|
runId: taskRun.id,
|
|
1316
1316
|
});
|
|
1317
|
-
this.logger.
|
|
1317
|
+
this.logger.debug("Built pending user prompt", {
|
|
1318
1318
|
hasMessage: typeof message === "string" && message.trim().length > 0,
|
|
1319
1319
|
requestedArtifactCount: artifactIds.length,
|
|
1320
1320
|
blockTypes: prompt.map((block) => block.type),
|