@posthog/agent 2.0.0 → 2.0.2
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/LICENSE +1 -1
- package/README.md +221 -219
- package/dist/adapters/claude/conversion/tool-use-to-acp.d.ts +21 -0
- package/dist/adapters/claude/conversion/tool-use-to-acp.js +547 -0
- package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -0
- package/dist/adapters/claude/permissions/permission-options.d.ts +13 -0
- package/dist/adapters/claude/permissions/permission-options.js +117 -0
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -0
- package/dist/adapters/claude/questions/utils.d.ts +132 -0
- package/dist/adapters/claude/questions/utils.js +63 -0
- package/dist/adapters/claude/questions/utils.js.map +1 -0
- package/dist/adapters/claude/tools.d.ts +18 -0
- package/dist/adapters/claude/tools.js +95 -0
- package/dist/adapters/claude/tools.js.map +1 -0
- package/dist/agent-DBQY1BfC.d.ts +123 -0
- package/dist/agent.d.ts +5 -0
- package/dist/agent.js +3656 -0
- package/dist/agent.js.map +1 -0
- package/dist/claude-cli/cli.js +3695 -2746
- package/dist/claude-cli/vendor/ripgrep/COPYING +3 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-darwin/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-darwin/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-linux/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-linux/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-darwin/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-darwin/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-linux/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-linux/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-win32/rg.exe +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-win32/ripgrep.node +0 -0
- package/dist/gateway-models.d.ts +24 -0
- package/dist/gateway-models.js +93 -0
- package/dist/gateway-models.js.map +1 -0
- package/dist/index.d.ts +170 -1157
- package/dist/index.js +9373 -5135
- package/dist/index.js.map +1 -1
- package/dist/logger-DDBiMOOD.d.ts +24 -0
- package/dist/posthog-api.d.ts +40 -0
- package/dist/posthog-api.js +175 -0
- package/dist/posthog-api.js.map +1 -0
- package/dist/server/agent-server.d.ts +41 -0
- package/dist/server/agent-server.js +10503 -0
- package/dist/server/agent-server.js.map +1 -0
- package/dist/server/bin.d.ts +1 -0
- package/dist/server/bin.js +10558 -0
- package/dist/server/bin.js.map +1 -0
- package/dist/types.d.ts +129 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +65 -13
- package/src/acp-extensions.ts +98 -16
- package/src/adapters/acp-connection.ts +494 -0
- package/src/adapters/base-acp-agent.ts +150 -0
- package/src/adapters/claude/claude-agent.ts +596 -0
- package/src/adapters/claude/conversion/acp-to-sdk.ts +102 -0
- package/src/adapters/claude/conversion/sdk-to-acp.ts +571 -0
- package/src/adapters/claude/conversion/tool-use-to-acp.ts +618 -0
- package/src/adapters/claude/hooks.ts +64 -0
- package/src/adapters/claude/mcp/tool-metadata.ts +102 -0
- package/src/adapters/claude/permissions/permission-handlers.ts +433 -0
- package/src/adapters/claude/permissions/permission-options.ts +103 -0
- package/src/adapters/claude/plan/utils.ts +56 -0
- package/src/adapters/claude/questions/utils.ts +92 -0
- package/src/adapters/claude/session/commands.ts +38 -0
- package/src/adapters/claude/session/mcp-config.ts +37 -0
- package/src/adapters/claude/session/models.ts +12 -0
- package/src/adapters/claude/session/options.ts +236 -0
- package/src/adapters/claude/tool-meta.ts +143 -0
- package/src/adapters/claude/tools.ts +53 -688
- package/src/adapters/claude/types.ts +61 -0
- package/src/adapters/codex/spawn.ts +130 -0
- package/src/agent.ts +96 -587
- package/src/execution-mode.ts +43 -0
- package/src/gateway-models.ts +135 -0
- package/src/index.ts +79 -0
- package/src/otel-log-writer.test.ts +105 -0
- package/src/otel-log-writer.ts +94 -0
- package/src/posthog-api.ts +75 -235
- package/src/resume.ts +115 -0
- package/src/sagas/apply-snapshot-saga.test.ts +690 -0
- package/src/sagas/apply-snapshot-saga.ts +88 -0
- package/src/sagas/capture-tree-saga.test.ts +892 -0
- package/src/sagas/capture-tree-saga.ts +141 -0
- package/src/sagas/resume-saga.test.ts +558 -0
- package/src/sagas/resume-saga.ts +332 -0
- package/src/sagas/test-fixtures.ts +250 -0
- package/src/server/agent-server.test.ts +220 -0
- package/src/server/agent-server.ts +748 -0
- package/src/server/bin.ts +88 -0
- package/src/server/jwt.ts +65 -0
- package/src/server/schemas.ts +47 -0
- package/src/server/types.ts +13 -0
- package/src/server/utils/retry.test.ts +122 -0
- package/src/server/utils/retry.ts +61 -0
- package/src/server/utils/sse-parser.test.ts +93 -0
- package/src/server/utils/sse-parser.ts +46 -0
- package/src/session-log-writer.test.ts +140 -0
- package/src/session-log-writer.ts +137 -0
- package/src/test/assertions.ts +114 -0
- package/src/test/controllers/sse-controller.ts +107 -0
- package/src/test/fixtures/api.ts +111 -0
- package/src/test/fixtures/config.ts +33 -0
- package/src/test/fixtures/notifications.ts +92 -0
- package/src/test/mocks/claude-sdk.ts +251 -0
- package/src/test/mocks/msw-handlers.ts +48 -0
- package/src/test/setup.ts +114 -0
- package/src/test/wait.ts +41 -0
- package/src/tree-tracker.ts +173 -0
- package/src/types.ts +54 -137
- package/src/utils/acp-content.ts +58 -0
- package/src/utils/async-mutex.test.ts +104 -0
- package/src/utils/async-mutex.ts +31 -0
- package/src/utils/common.ts +15 -0
- package/src/utils/gateway.ts +9 -6
- package/src/utils/logger.ts +0 -30
- package/src/utils/streams.ts +220 -0
- package/CLAUDE.md +0 -331
- package/src/adapters/claude/claude.ts +0 -1947
- package/src/adapters/claude/mcp-server.ts +0 -810
- package/src/adapters/claude/utils.ts +0 -267
- package/src/adapters/connection.ts +0 -95
- package/src/file-manager.ts +0 -273
- package/src/git-manager.ts +0 -577
- package/src/schemas.ts +0 -241
- package/src/session-store.ts +0 -259
- package/src/task-manager.ts +0 -163
- package/src/todo-manager.ts +0 -180
- package/src/tools/registry.ts +0 -134
- package/src/tools/types.ts +0 -133
- package/src/utils/tapped-stream.ts +0 -60
- package/src/worktree-manager.ts +0 -974
package/src/agent.ts
CHANGED
|
@@ -1,299 +1,142 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Client,
|
|
3
|
-
ClientSideConnection,
|
|
4
|
-
type ContentBlock,
|
|
5
|
-
ndJsonStream,
|
|
6
|
-
PROTOCOL_VERSION,
|
|
7
|
-
} from "@agentclientprotocol/sdk";
|
|
8
|
-
import { POSTHOG_NOTIFICATIONS } from "./acp-extensions.js";
|
|
9
1
|
import {
|
|
10
2
|
createAcpConnection,
|
|
11
3
|
type InProcessAcpConnection,
|
|
12
|
-
} from "./adapters/connection.js";
|
|
13
|
-
import {
|
|
14
|
-
|
|
4
|
+
} from "./adapters/acp-connection.js";
|
|
5
|
+
import {
|
|
6
|
+
BLOCKED_MODELS,
|
|
7
|
+
DEFAULT_GATEWAY_MODEL,
|
|
8
|
+
fetchArrayModels,
|
|
9
|
+
} from "./gateway-models.js";
|
|
15
10
|
import { PostHogAPIClient } from "./posthog-api.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import type {
|
|
19
|
-
AgentConfig,
|
|
20
|
-
CanUseTool,
|
|
21
|
-
StoredNotification,
|
|
22
|
-
Task,
|
|
23
|
-
TaskExecutionOptions,
|
|
24
|
-
} from "./types.js";
|
|
11
|
+
import { SessionLogWriter } from "./session-log-writer.js";
|
|
12
|
+
import type { AgentConfig, TaskExecutionOptions } from "./types.js";
|
|
25
13
|
import { Logger } from "./utils/logger.js";
|
|
26
14
|
|
|
27
|
-
/**
|
|
28
|
-
* Type for sending ACP notifications
|
|
29
|
-
*/
|
|
30
|
-
type SendNotification = (
|
|
31
|
-
method: string,
|
|
32
|
-
params: Record<string, unknown>,
|
|
33
|
-
) => Promise<void>;
|
|
34
|
-
|
|
35
15
|
export class Agent {
|
|
36
|
-
private workingDirectory: string;
|
|
37
|
-
private taskManager: TaskManager;
|
|
38
16
|
private posthogAPI?: PostHogAPIClient;
|
|
39
|
-
private fileManager: PostHogFileManager;
|
|
40
|
-
private gitManager: GitManager;
|
|
41
17
|
private logger: Logger;
|
|
42
18
|
private acpConnection?: InProcessAcpConnection;
|
|
43
|
-
private
|
|
44
|
-
private
|
|
45
|
-
private currentRunId?: string;
|
|
46
|
-
private sessionStore?: SessionStore;
|
|
19
|
+
private taskRunId?: string;
|
|
20
|
+
private sessionLogWriter?: SessionLogWriter;
|
|
47
21
|
public debug: boolean;
|
|
48
22
|
|
|
49
23
|
constructor(config: AgentConfig) {
|
|
50
|
-
this.workingDirectory = config.workingDirectory || process.cwd();
|
|
51
|
-
this.canUseTool = config.canUseTool;
|
|
52
24
|
this.debug = config.debug || false;
|
|
53
|
-
|
|
54
|
-
// Build default PostHog MCP server configuration
|
|
55
|
-
const posthogMcpUrl =
|
|
56
|
-
config.posthogMcpUrl ||
|
|
57
|
-
process.env.POSTHOG_MCP_URL ||
|
|
58
|
-
"https://mcp.posthog.com/mcp";
|
|
59
|
-
|
|
60
|
-
// Add auth if API key provided
|
|
61
|
-
const headers: Record<string, string> = {};
|
|
62
|
-
if (config.getPosthogApiKey) {
|
|
63
|
-
headers.Authorization = `Bearer ${config.getPosthogApiKey()}`;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const defaultMcpServers = {
|
|
67
|
-
posthog: {
|
|
68
|
-
type: "http" as const,
|
|
69
|
-
url: posthogMcpUrl,
|
|
70
|
-
...(Object.keys(headers).length > 0 ? { headers } : {}),
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
// Merge default PostHog MCP with user-provided servers (user config takes precedence)
|
|
75
|
-
this.mcpServers = {
|
|
76
|
-
...defaultMcpServers,
|
|
77
|
-
...config.mcpServers,
|
|
78
|
-
};
|
|
79
25
|
this.logger = new Logger({
|
|
80
26
|
debug: this.debug,
|
|
81
27
|
prefix: "[PostHog Agent]",
|
|
82
28
|
onLog: config.onLog,
|
|
83
29
|
});
|
|
84
|
-
this.taskManager = new TaskManager();
|
|
85
30
|
|
|
86
|
-
|
|
87
|
-
this.
|
|
88
|
-
|
|
89
|
-
);
|
|
90
|
-
this.gitManager = new GitManager({
|
|
91
|
-
repositoryPath: this.workingDirectory,
|
|
92
|
-
logger: this.logger.child("GitManager"),
|
|
93
|
-
});
|
|
31
|
+
if (config.posthog) {
|
|
32
|
+
this.posthogAPI = new PostHogAPIClient(config.posthog);
|
|
33
|
+
}
|
|
94
34
|
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
35
|
+
if (config.otelTransport) {
|
|
36
|
+
// OTEL pipeline: use OtelLogWriter only (no S3 writer)
|
|
37
|
+
this.sessionLogWriter = new SessionLogWriter({
|
|
38
|
+
otelConfig: {
|
|
39
|
+
posthogHost: config.otelTransport.host,
|
|
40
|
+
apiKey: config.otelTransport.apiKey,
|
|
41
|
+
logsPath: config.otelTransport.logsPath,
|
|
42
|
+
},
|
|
43
|
+
logger: this.logger.child("SessionLogWriter"),
|
|
44
|
+
});
|
|
45
|
+
} else if (config.posthog) {
|
|
46
|
+
// Legacy: use S3 writer via PostHog API
|
|
47
|
+
this.sessionLogWriter = new SessionLogWriter({
|
|
48
|
+
posthogAPI: this.posthogAPI,
|
|
49
|
+
logger: this.logger.child("SessionLogWriter"),
|
|
104
50
|
});
|
|
105
|
-
|
|
106
|
-
// Create SessionStore from the API client for ACP connection
|
|
107
|
-
this.sessionStore = new SessionStore(
|
|
108
|
-
this.posthogAPI,
|
|
109
|
-
this.logger.child("SessionStore"),
|
|
110
|
-
);
|
|
111
51
|
}
|
|
112
52
|
}
|
|
113
53
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
this.debug = enabled;
|
|
119
|
-
this.logger.setDebug(enabled);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Configure LLM gateway environment variables for Claude Code CLI.
|
|
124
|
-
*/
|
|
125
|
-
private async _configureLlmGateway(): Promise<void> {
|
|
54
|
+
private _configureLlmGateway(_adapter?: "claude" | "codex"): {
|
|
55
|
+
gatewayUrl: string;
|
|
56
|
+
apiKey: string;
|
|
57
|
+
} | null {
|
|
126
58
|
if (!this.posthogAPI) {
|
|
127
|
-
return;
|
|
59
|
+
return null;
|
|
128
60
|
}
|
|
129
61
|
|
|
130
62
|
try {
|
|
131
63
|
const gatewayUrl = this.posthogAPI.getLlmGatewayUrl();
|
|
132
64
|
const apiKey = this.posthogAPI.getApiKey();
|
|
65
|
+
|
|
66
|
+
process.env.OPENAI_BASE_URL = `${gatewayUrl}/v1`;
|
|
67
|
+
process.env.OPENAI_API_KEY = apiKey;
|
|
133
68
|
process.env.ANTHROPIC_BASE_URL = gatewayUrl;
|
|
134
69
|
process.env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
135
|
-
|
|
136
|
-
|
|
70
|
+
|
|
71
|
+
return { gatewayUrl, apiKey };
|
|
137
72
|
} catch (error) {
|
|
138
73
|
this.logger.error("Failed to configure LLM gateway", error);
|
|
139
74
|
throw error;
|
|
140
75
|
}
|
|
141
76
|
}
|
|
142
77
|
|
|
143
|
-
|
|
144
|
-
* @deprecated Use runTaskV2() for local execution or runTaskCloud() for cloud execution.
|
|
145
|
-
* This method used the old workflow system which has been removed.
|
|
146
|
-
*/
|
|
147
|
-
async runTask(
|
|
148
|
-
_taskId: string,
|
|
149
|
-
_taskRunId: string,
|
|
150
|
-
_options: import("./types.js").TaskExecutionOptions = {},
|
|
151
|
-
): Promise<void> {
|
|
152
|
-
throw new Error(
|
|
153
|
-
"runTask() is deprecated. Use runTaskV2() for local execution or runTaskCloud() for cloud execution.",
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Creates an in-process ACP connection for client communication.
|
|
159
|
-
* Sets up git branch for the task, configures LLM gateway.
|
|
160
|
-
* The client handles all prompting/querying via the returned streams.
|
|
161
|
-
*
|
|
162
|
-
* @returns InProcessAcpConnection with clientStreams for the client to use
|
|
163
|
-
*/
|
|
164
|
-
async runTaskV2(
|
|
78
|
+
async run(
|
|
165
79
|
taskId: string,
|
|
166
80
|
taskRunId: string,
|
|
167
|
-
options:
|
|
81
|
+
options: TaskExecutionOptions = {},
|
|
168
82
|
): Promise<InProcessAcpConnection> {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
taskId,
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
const sendNotification: SendNotification = async (method, params) => {
|
|
185
|
-
this.logger.debug(`Notification: ${method}`, params);
|
|
186
|
-
await this.acpConnection?.agentConnection.extNotification?.(
|
|
187
|
-
method,
|
|
188
|
-
params,
|
|
189
|
-
);
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
if (!options.isReconnect) {
|
|
193
|
-
await sendNotification(POSTHOG_NOTIFICATIONS.RUN_STARTED, {
|
|
194
|
-
sessionId: taskRunId,
|
|
195
|
-
runId: taskRunId,
|
|
83
|
+
const gatewayConfig = this._configureLlmGateway(options.adapter);
|
|
84
|
+
|
|
85
|
+
this.taskRunId = taskRunId;
|
|
86
|
+
|
|
87
|
+
let allowedModelIds: Set<string> | undefined;
|
|
88
|
+
let sanitizedModel =
|
|
89
|
+
options.model && !BLOCKED_MODELS.has(options.model)
|
|
90
|
+
? options.model
|
|
91
|
+
: undefined;
|
|
92
|
+
if (options.adapter === "codex" && gatewayConfig) {
|
|
93
|
+
const models = await fetchArrayModels({
|
|
94
|
+
gatewayUrl: gatewayConfig.gatewayUrl,
|
|
196
95
|
});
|
|
197
|
-
|
|
96
|
+
const codexModelIds = models
|
|
97
|
+
.filter((model) => {
|
|
98
|
+
if (BLOCKED_MODELS.has(model.id)) return false;
|
|
99
|
+
if (model.owned_by) {
|
|
100
|
+
return model.owned_by === "openai";
|
|
101
|
+
}
|
|
102
|
+
return model.id.startsWith("gpt-") || model.id.startsWith("openai/");
|
|
103
|
+
})
|
|
104
|
+
.map((model) => model.id);
|
|
198
105
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const task = options.task ?? (await this.fetchTask(taskId));
|
|
202
|
-
const taskSlug = (task as any).slug || task.id;
|
|
203
|
-
try {
|
|
204
|
-
await this.prepareTaskBranch(taskSlug, isCloudMode, sendNotification);
|
|
205
|
-
} catch (error) {
|
|
206
|
-
const errorMessage =
|
|
207
|
-
error instanceof Error ? error.message : String(error);
|
|
208
|
-
this.logger.error("Failed to prepare task branch", {
|
|
209
|
-
error: errorMessage,
|
|
210
|
-
});
|
|
211
|
-
await sendNotification(POSTHOG_NOTIFICATIONS.ERROR, {
|
|
212
|
-
sessionId: taskRunId,
|
|
213
|
-
message: errorMessage,
|
|
214
|
-
});
|
|
215
|
-
throw error;
|
|
106
|
+
if (codexModelIds.length > 0) {
|
|
107
|
+
allowedModelIds = new Set(codexModelIds);
|
|
216
108
|
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return this.acpConnection;
|
|
220
|
-
}
|
|
221
109
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const error = new Error(
|
|
226
|
-
"PostHog API not configured. Provide posthogApiUrl and posthogApiKey in constructor.",
|
|
227
|
-
);
|
|
228
|
-
this.logger.error("PostHog API not configured", error);
|
|
229
|
-
throw error;
|
|
110
|
+
if (!sanitizedModel || !allowedModelIds?.has(sanitizedModel)) {
|
|
111
|
+
sanitizedModel = codexModelIds[0];
|
|
112
|
+
}
|
|
230
113
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
getPostHogClient(): PostHogAPIClient | undefined {
|
|
235
|
-
return this.posthogAPI;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Send a notification to a cloud task run's S3 log.
|
|
240
|
-
* The cloud runner will pick up new notifications via interrupt polling.
|
|
241
|
-
*/
|
|
242
|
-
async sendNotification(
|
|
243
|
-
taskId: string,
|
|
244
|
-
runId: string,
|
|
245
|
-
notification: StoredNotification,
|
|
246
|
-
): Promise<void> {
|
|
247
|
-
if (!this.posthogAPI) {
|
|
248
|
-
throw new Error(
|
|
249
|
-
"PostHog API not configured. Cannot send notification to cloud task.",
|
|
250
|
-
);
|
|
114
|
+
if (!sanitizedModel) {
|
|
115
|
+
sanitizedModel = DEFAULT_GATEWAY_MODEL;
|
|
251
116
|
}
|
|
252
117
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
method: notification.notification.method,
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
async getTaskFiles(taskId: string): Promise<any[]> {
|
|
262
|
-
this.logger.debug("Getting task files", { taskId });
|
|
263
|
-
const files = await this.fileManager.getTaskFiles(taskId);
|
|
264
|
-
this.logger.debug("Found task files", { taskId, fileCount: files.length });
|
|
265
|
-
return files;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
async createPullRequest(
|
|
269
|
-
taskId: string,
|
|
270
|
-
branchName: string,
|
|
271
|
-
taskTitle: string,
|
|
272
|
-
taskDescription: string,
|
|
273
|
-
customBody?: string,
|
|
274
|
-
): Promise<string> {
|
|
275
|
-
this.logger.info("Creating pull request", {
|
|
118
|
+
this.acpConnection = createAcpConnection({
|
|
119
|
+
adapter: options.adapter,
|
|
120
|
+
logWriter: this.sessionLogWriter,
|
|
121
|
+
taskRunId,
|
|
276
122
|
taskId,
|
|
277
|
-
|
|
278
|
-
|
|
123
|
+
deviceType: "local",
|
|
124
|
+
logger: this.logger,
|
|
125
|
+
processCallbacks: options.processCallbacks,
|
|
126
|
+
allowedModelIds,
|
|
127
|
+
codexOptions:
|
|
128
|
+
options.adapter === "codex" && gatewayConfig
|
|
129
|
+
? {
|
|
130
|
+
cwd: options.repositoryPath,
|
|
131
|
+
apiBaseUrl: `${gatewayConfig.gatewayUrl}/v1`,
|
|
132
|
+
apiKey: gatewayConfig.apiKey,
|
|
133
|
+
binaryPath: options.codexBinaryPath,
|
|
134
|
+
model: sanitizedModel,
|
|
135
|
+
}
|
|
136
|
+
: undefined,
|
|
279
137
|
});
|
|
280
138
|
|
|
281
|
-
|
|
282
|
-
**Task ID**: ${taskId}
|
|
283
|
-
**Description**: ${taskDescription}
|
|
284
|
-
|
|
285
|
-
## Changes
|
|
286
|
-
This PR implements the changes described in the task.`;
|
|
287
|
-
const prBody = customBody || defaultBody;
|
|
288
|
-
|
|
289
|
-
const prUrl = await this.gitManager.createPullRequest(
|
|
290
|
-
branchName,
|
|
291
|
-
taskTitle,
|
|
292
|
-
prBody,
|
|
293
|
-
);
|
|
294
|
-
|
|
295
|
-
this.logger.info("Pull request created", { taskId, prUrl });
|
|
296
|
-
return prUrl;
|
|
139
|
+
return this.acpConnection;
|
|
297
140
|
}
|
|
298
141
|
|
|
299
142
|
async attachPullRequestToTask(
|
|
@@ -303,7 +146,7 @@ This PR implements the changes described in the task.`;
|
|
|
303
146
|
): Promise<void> {
|
|
304
147
|
this.logger.info("Attaching PR to task run", { taskId, prUrl, branchName });
|
|
305
148
|
|
|
306
|
-
if (!this.posthogAPI || !this.
|
|
149
|
+
if (!this.posthogAPI || !this.taskRunId) {
|
|
307
150
|
const error = new Error(
|
|
308
151
|
"PostHog API not configured or no active run. Cannot attach PR to task.",
|
|
309
152
|
);
|
|
@@ -318,356 +161,22 @@ This PR implements the changes described in the task.`;
|
|
|
318
161
|
updates.branch = branchName;
|
|
319
162
|
}
|
|
320
163
|
|
|
321
|
-
await this.posthogAPI.updateTaskRun(taskId, this.
|
|
164
|
+
await this.posthogAPI.updateTaskRun(taskId, this.taskRunId, updates);
|
|
322
165
|
this.logger.debug("PR attached to task run", {
|
|
323
166
|
taskId,
|
|
324
|
-
|
|
167
|
+
taskRunId: this.taskRunId,
|
|
325
168
|
prUrl,
|
|
326
169
|
});
|
|
327
170
|
}
|
|
328
171
|
|
|
329
|
-
async
|
|
330
|
-
this.
|
|
331
|
-
|
|
332
|
-
if (!this.posthogAPI || !this.currentRunId) {
|
|
333
|
-
const error = new Error(
|
|
334
|
-
"PostHog API not configured or no active run. Cannot update branch.",
|
|
335
|
-
);
|
|
336
|
-
this.logger.error("PostHog API not configured", error);
|
|
337
|
-
throw error;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
await this.posthogAPI.updateTaskRun(taskId, this.currentRunId, {
|
|
341
|
-
branch: branchName,
|
|
342
|
-
});
|
|
343
|
-
this.logger.debug("Task run branch updated", {
|
|
344
|
-
taskId,
|
|
345
|
-
runId: this.currentRunId,
|
|
346
|
-
branchName,
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Execution management
|
|
351
|
-
cancelTask(taskId: string): void {
|
|
352
|
-
// Find the execution for this task and cancel it
|
|
353
|
-
for (const [executionId, execution] of this.taskManager.executionStates) {
|
|
354
|
-
if (execution.taskId === taskId && execution.status === "running") {
|
|
355
|
-
this.taskManager.cancelExecution(executionId);
|
|
356
|
-
break;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
getTaskExecutionStatus(taskId: string): string | null {
|
|
362
|
-
// Find the execution for this task
|
|
363
|
-
for (const execution of this.taskManager.executionStates.values()) {
|
|
364
|
-
if (execution.taskId === taskId) {
|
|
365
|
-
return execution.status;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
return null;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
private async prepareTaskBranch(
|
|
372
|
-
taskSlug: string,
|
|
373
|
-
isCloudMode: boolean,
|
|
374
|
-
sendNotification: SendNotification,
|
|
375
|
-
): Promise<void> {
|
|
376
|
-
if (await this.gitManager.hasChanges()) {
|
|
377
|
-
throw new Error(
|
|
378
|
-
"Cannot start task with uncommitted changes. Please commit or stash your changes first.",
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// If we're running in a worktree, we're already on the correct branch
|
|
383
|
-
// (the worktree was created with its own branch). Skip branch creation.
|
|
384
|
-
const isWorktree = await this.gitManager.isWorktree();
|
|
385
|
-
if (isWorktree) {
|
|
386
|
-
const currentBranch = await this.gitManager.getCurrentBranch();
|
|
387
|
-
this.logger.info("Running in worktree, using existing branch", {
|
|
388
|
-
branch: currentBranch,
|
|
389
|
-
});
|
|
390
|
-
await sendNotification(POSTHOG_NOTIFICATIONS.BRANCH_CREATED, {
|
|
391
|
-
branch: currentBranch,
|
|
392
|
-
});
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
await this.gitManager.resetToDefaultBranchIfNeeded();
|
|
397
|
-
|
|
398
|
-
const existingBranch = await this.gitManager.getTaskBranch(taskSlug);
|
|
399
|
-
if (!existingBranch) {
|
|
400
|
-
const branchName = await this.gitManager.createTaskBranch(taskSlug);
|
|
401
|
-
await sendNotification(POSTHOG_NOTIFICATIONS.BRANCH_CREATED, {
|
|
402
|
-
branch: branchName,
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
await this.gitManager.addAllPostHogFiles();
|
|
406
|
-
|
|
407
|
-
// Only commit if there are changes or we're in cloud mode
|
|
408
|
-
if (isCloudMode) {
|
|
409
|
-
await this.gitManager.commitAndPush(`Initialize task ${taskSlug}`, {
|
|
410
|
-
allowEmpty: true,
|
|
411
|
-
});
|
|
412
|
-
} else {
|
|
413
|
-
// Check if there are any changes before committing
|
|
414
|
-
const hasChanges = await this.gitManager.hasStagedChanges();
|
|
415
|
-
if (hasChanges) {
|
|
416
|
-
await this.gitManager.commitChanges(`Initialize task ${taskSlug}`);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
} else {
|
|
420
|
-
this.logger.info("Switching to existing task branch", {
|
|
421
|
-
branch: existingBranch,
|
|
422
|
-
});
|
|
423
|
-
await this.gitManager.switchToBranch(existingBranch);
|
|
424
|
-
}
|
|
172
|
+
async flushAllLogs(): Promise<void> {
|
|
173
|
+
await this.sessionLogWriter?.flushAll();
|
|
425
174
|
}
|
|
426
175
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if (resolvedGatewayUrl) {
|
|
432
|
-
process.env.OPENAI_BASE_URL = resolvedGatewayUrl;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
if (resolvedToken) {
|
|
436
|
-
process.env.OPENAI_API_KEY = resolvedToken;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
private ensureGeminiGatewayEnv(gatewayUrl?: string, token?: string): void {
|
|
441
|
-
const resolvedGatewayUrl = gatewayUrl || process.env.ANTHROPIC_BASE_URL;
|
|
442
|
-
const resolvedToken = token || process.env.ANTHROPIC_AUTH_TOKEN;
|
|
443
|
-
|
|
444
|
-
if (resolvedGatewayUrl) {
|
|
445
|
-
process.env.GEMINI_BASE_URL = resolvedGatewayUrl;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if (resolvedToken) {
|
|
449
|
-
process.env.GEMINI_API_KEY = resolvedToken;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
async runTaskCloud(
|
|
454
|
-
taskId: string,
|
|
455
|
-
taskRunId: string,
|
|
456
|
-
options: TaskExecutionOptions = {},
|
|
457
|
-
): Promise<void> {
|
|
458
|
-
await this._configureLlmGateway();
|
|
459
|
-
|
|
460
|
-
const task = await this.fetchTask(taskId);
|
|
461
|
-
const cwd = options.repositoryPath || this.workingDirectory;
|
|
462
|
-
const taskSlug = (task as any).slug || task.id;
|
|
463
|
-
|
|
464
|
-
this.currentRunId = taskRunId;
|
|
465
|
-
|
|
466
|
-
this.logger.info("Starting cloud task execution", {
|
|
467
|
-
taskId: task.id,
|
|
468
|
-
taskSlug,
|
|
469
|
-
taskRunId,
|
|
470
|
-
cwd,
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
if (!this.sessionStore) {
|
|
474
|
-
throw new Error(
|
|
475
|
-
"SessionStore required for cloud mode. Ensure PostHog API credentials are configured.",
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// Start session in SessionStore (updates task run status to in_progress)
|
|
480
|
-
const taskRun = await this.sessionStore.start(taskRunId, taskId, taskRunId);
|
|
481
|
-
this.logger.debug("Session started", {
|
|
482
|
-
taskRunId,
|
|
483
|
-
logUrl: taskRun?.log_url,
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
// Create internal ACP connection with S3 persistence
|
|
487
|
-
const acpConnection = createAcpConnection({
|
|
488
|
-
sessionStore: this.sessionStore,
|
|
489
|
-
sessionId: taskRunId,
|
|
490
|
-
taskId: task.id,
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
// Create client connection using the client-side streams
|
|
494
|
-
const clientStream = ndJsonStream(
|
|
495
|
-
acpConnection.clientStreams.writable as WritableStream<Uint8Array>,
|
|
496
|
-
acpConnection.clientStreams.readable as ReadableStream<Uint8Array>,
|
|
497
|
-
);
|
|
498
|
-
|
|
499
|
-
// Create auto-approving client for headless cloud mode
|
|
500
|
-
const cloudClient: Client = {
|
|
501
|
-
async requestPermission(params) {
|
|
502
|
-
const allowOption = params.options.find(
|
|
503
|
-
(o) => o.kind === "allow_once" || o.kind === "allow_always",
|
|
504
|
-
);
|
|
505
|
-
return {
|
|
506
|
-
outcome: {
|
|
507
|
-
outcome: "selected",
|
|
508
|
-
optionId: allowOption?.optionId ?? params.options[0].optionId,
|
|
509
|
-
},
|
|
510
|
-
};
|
|
511
|
-
},
|
|
512
|
-
async sessionUpdate(_params) {
|
|
513
|
-
// Notifications are already being persisted to S3 via tapped streams
|
|
514
|
-
},
|
|
515
|
-
};
|
|
516
|
-
|
|
517
|
-
const clientConnection = new ClientSideConnection(
|
|
518
|
-
(_agent) => cloudClient,
|
|
519
|
-
clientStream,
|
|
520
|
-
);
|
|
521
|
-
|
|
522
|
-
try {
|
|
523
|
-
// Initialize the connection
|
|
524
|
-
await clientConnection.initialize({
|
|
525
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
526
|
-
clientCapabilities: {},
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
// Create new session
|
|
530
|
-
await clientConnection.newSession({
|
|
531
|
-
cwd,
|
|
532
|
-
mcpServers: [],
|
|
533
|
-
_meta: { sessionId: taskRunId },
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
// Prepare git branch if not skipped
|
|
537
|
-
if (!options.skipGitBranch) {
|
|
538
|
-
const sendNotification: SendNotification = async (method, params) => {
|
|
539
|
-
this.logger.debug(`Notification: ${method}`, params);
|
|
540
|
-
await acpConnection.agentConnection.extNotification?.(method, params);
|
|
541
|
-
};
|
|
542
|
-
await this.prepareTaskBranch(taskSlug, true, sendNotification);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// Build initial prompt from task description
|
|
546
|
-
const initialPrompt: ContentBlock[] = [
|
|
547
|
-
{
|
|
548
|
-
type: "text",
|
|
549
|
-
text: `# Task: ${task.title}\n\n${task.description}`,
|
|
550
|
-
},
|
|
551
|
-
];
|
|
552
|
-
|
|
553
|
-
// Track the last known log entry count for interrupt polling
|
|
554
|
-
let lastKnownEntryCount = 0;
|
|
555
|
-
let isPolling = true;
|
|
556
|
-
|
|
557
|
-
// Start interrupt polling in background
|
|
558
|
-
const pollForInterrupts = async () => {
|
|
559
|
-
while (isPolling) {
|
|
560
|
-
await new Promise((resolve) => setTimeout(resolve, 2000)); // Poll every 2 seconds
|
|
561
|
-
if (!isPolling) break;
|
|
562
|
-
|
|
563
|
-
try {
|
|
564
|
-
const newEntries = await this.sessionStore?.pollForNewEntries(
|
|
565
|
-
taskRunId,
|
|
566
|
-
lastKnownEntryCount,
|
|
567
|
-
);
|
|
568
|
-
|
|
569
|
-
for (const entry of newEntries ?? []) {
|
|
570
|
-
lastKnownEntryCount++;
|
|
571
|
-
// Look for user_message notifications
|
|
572
|
-
if (
|
|
573
|
-
entry.notification?.method === "sessionUpdate" &&
|
|
574
|
-
(entry.notification?.params as any)?.sessionUpdate ===
|
|
575
|
-
"user_message"
|
|
576
|
-
) {
|
|
577
|
-
const content = (entry.notification?.params as any)?.content;
|
|
578
|
-
if (content) {
|
|
579
|
-
this.logger.info("Processing user interrupt", { content });
|
|
580
|
-
// Send as new prompt - will be processed after current prompt completes
|
|
581
|
-
await clientConnection.prompt({
|
|
582
|
-
sessionId: taskRunId,
|
|
583
|
-
prompt: Array.isArray(content) ? content : [content],
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
} catch (err) {
|
|
589
|
-
this.logger.warn("Interrupt polling error", { error: err });
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
// Start polling in background (don't await)
|
|
595
|
-
const pollingPromise = pollForInterrupts();
|
|
596
|
-
|
|
597
|
-
// Send initial prompt and wait for completion
|
|
598
|
-
this.logger.info("Sending initial prompt to agent");
|
|
599
|
-
const result = await clientConnection.prompt({
|
|
600
|
-
sessionId: taskRunId,
|
|
601
|
-
prompt: initialPrompt,
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
// Stop interrupt polling
|
|
605
|
-
isPolling = false;
|
|
606
|
-
await pollingPromise;
|
|
607
|
-
|
|
608
|
-
this.logger.info("Task execution complete", {
|
|
609
|
-
taskId: task.id,
|
|
610
|
-
stopReason: result.stopReason,
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
const branchName = await this.gitManager.getCurrentBranch();
|
|
614
|
-
const hasChanges = await this.gitManager.hasChanges();
|
|
615
|
-
const shouldCreatePR = options.createPR ?? false;
|
|
616
|
-
|
|
617
|
-
if (hasChanges) {
|
|
618
|
-
this.logger.info("Committing uncommitted changes", { taskId: task.id });
|
|
619
|
-
await this.gitManager.commitImplementation(
|
|
620
|
-
task.id,
|
|
621
|
-
task.title,
|
|
622
|
-
task.description ?? undefined,
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
const defaultBranch = await this.gitManager.getDefaultBranch();
|
|
627
|
-
if (branchName !== defaultBranch) {
|
|
628
|
-
this.logger.info("Pushing branch", { branchName, taskId: task.id });
|
|
629
|
-
await this.gitManager.pushBranch(branchName);
|
|
630
|
-
|
|
631
|
-
if (shouldCreatePR) {
|
|
632
|
-
this.logger.info("Creating PR", { branchName, taskId: task.id });
|
|
633
|
-
|
|
634
|
-
const prUrl = await this.createPullRequest(
|
|
635
|
-
task.id,
|
|
636
|
-
branchName,
|
|
637
|
-
task.title,
|
|
638
|
-
task.description ?? "",
|
|
639
|
-
);
|
|
640
|
-
|
|
641
|
-
this.logger.info("PR created", { prUrl, taskId: task.id });
|
|
642
|
-
|
|
643
|
-
try {
|
|
644
|
-
await this.attachPullRequestToTask(task.id, prUrl, branchName);
|
|
645
|
-
} catch (err) {
|
|
646
|
-
this.logger.warn("Could not attach PR to task", {
|
|
647
|
-
error: err instanceof Error ? err.message : String(err),
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
await this.sessionStore.complete(taskRunId);
|
|
654
|
-
} catch (error) {
|
|
655
|
-
const errorMessage =
|
|
656
|
-
error instanceof Error ? error.message : String(error);
|
|
657
|
-
this.logger.error("Cloud task execution failed", {
|
|
658
|
-
taskId: task.id,
|
|
659
|
-
error: errorMessage,
|
|
660
|
-
});
|
|
661
|
-
await this.sessionStore.fail(taskRunId, errorMessage);
|
|
662
|
-
throw error;
|
|
176
|
+
async cleanup(): Promise<void> {
|
|
177
|
+
if (this.sessionLogWriter && this.taskRunId) {
|
|
178
|
+
await this.sessionLogWriter.flush(this.taskRunId);
|
|
663
179
|
}
|
|
180
|
+
await this.acpConnection?.cleanup();
|
|
664
181
|
}
|
|
665
182
|
}
|
|
666
|
-
|
|
667
|
-
export type {
|
|
668
|
-
AgentConfig,
|
|
669
|
-
ExecutionResult,
|
|
670
|
-
SupportingFile,
|
|
671
|
-
Task,
|
|
672
|
-
} from "./types.js";
|
|
673
|
-
export { PermissionMode } from "./types.js";
|