@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.
Files changed (131) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +221 -219
  3. package/dist/adapters/claude/conversion/tool-use-to-acp.d.ts +21 -0
  4. package/dist/adapters/claude/conversion/tool-use-to-acp.js +547 -0
  5. package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -0
  6. package/dist/adapters/claude/permissions/permission-options.d.ts +13 -0
  7. package/dist/adapters/claude/permissions/permission-options.js +117 -0
  8. package/dist/adapters/claude/permissions/permission-options.js.map +1 -0
  9. package/dist/adapters/claude/questions/utils.d.ts +132 -0
  10. package/dist/adapters/claude/questions/utils.js +63 -0
  11. package/dist/adapters/claude/questions/utils.js.map +1 -0
  12. package/dist/adapters/claude/tools.d.ts +18 -0
  13. package/dist/adapters/claude/tools.js +95 -0
  14. package/dist/adapters/claude/tools.js.map +1 -0
  15. package/dist/agent-DBQY1BfC.d.ts +123 -0
  16. package/dist/agent.d.ts +5 -0
  17. package/dist/agent.js +3656 -0
  18. package/dist/agent.js.map +1 -0
  19. package/dist/claude-cli/cli.js +3695 -2746
  20. package/dist/claude-cli/vendor/ripgrep/COPYING +3 -0
  21. package/dist/claude-cli/vendor/ripgrep/arm64-darwin/rg +0 -0
  22. package/dist/claude-cli/vendor/ripgrep/arm64-darwin/ripgrep.node +0 -0
  23. package/dist/claude-cli/vendor/ripgrep/arm64-linux/rg +0 -0
  24. package/dist/claude-cli/vendor/ripgrep/arm64-linux/ripgrep.node +0 -0
  25. package/dist/claude-cli/vendor/ripgrep/x64-darwin/rg +0 -0
  26. package/dist/claude-cli/vendor/ripgrep/x64-darwin/ripgrep.node +0 -0
  27. package/dist/claude-cli/vendor/ripgrep/x64-linux/rg +0 -0
  28. package/dist/claude-cli/vendor/ripgrep/x64-linux/ripgrep.node +0 -0
  29. package/dist/claude-cli/vendor/ripgrep/x64-win32/rg.exe +0 -0
  30. package/dist/claude-cli/vendor/ripgrep/x64-win32/ripgrep.node +0 -0
  31. package/dist/gateway-models.d.ts +24 -0
  32. package/dist/gateway-models.js +93 -0
  33. package/dist/gateway-models.js.map +1 -0
  34. package/dist/index.d.ts +170 -1157
  35. package/dist/index.js +9373 -5135
  36. package/dist/index.js.map +1 -1
  37. package/dist/logger-DDBiMOOD.d.ts +24 -0
  38. package/dist/posthog-api.d.ts +40 -0
  39. package/dist/posthog-api.js +175 -0
  40. package/dist/posthog-api.js.map +1 -0
  41. package/dist/server/agent-server.d.ts +41 -0
  42. package/dist/server/agent-server.js +10503 -0
  43. package/dist/server/agent-server.js.map +1 -0
  44. package/dist/server/bin.d.ts +1 -0
  45. package/dist/server/bin.js +10558 -0
  46. package/dist/server/bin.js.map +1 -0
  47. package/dist/types.d.ts +129 -0
  48. package/dist/types.js +1 -0
  49. package/dist/types.js.map +1 -0
  50. package/package.json +65 -13
  51. package/src/acp-extensions.ts +98 -16
  52. package/src/adapters/acp-connection.ts +494 -0
  53. package/src/adapters/base-acp-agent.ts +150 -0
  54. package/src/adapters/claude/claude-agent.ts +596 -0
  55. package/src/adapters/claude/conversion/acp-to-sdk.ts +102 -0
  56. package/src/adapters/claude/conversion/sdk-to-acp.ts +571 -0
  57. package/src/adapters/claude/conversion/tool-use-to-acp.ts +618 -0
  58. package/src/adapters/claude/hooks.ts +64 -0
  59. package/src/adapters/claude/mcp/tool-metadata.ts +102 -0
  60. package/src/adapters/claude/permissions/permission-handlers.ts +433 -0
  61. package/src/adapters/claude/permissions/permission-options.ts +103 -0
  62. package/src/adapters/claude/plan/utils.ts +56 -0
  63. package/src/adapters/claude/questions/utils.ts +92 -0
  64. package/src/adapters/claude/session/commands.ts +38 -0
  65. package/src/adapters/claude/session/mcp-config.ts +37 -0
  66. package/src/adapters/claude/session/models.ts +12 -0
  67. package/src/adapters/claude/session/options.ts +236 -0
  68. package/src/adapters/claude/tool-meta.ts +143 -0
  69. package/src/adapters/claude/tools.ts +53 -688
  70. package/src/adapters/claude/types.ts +61 -0
  71. package/src/adapters/codex/spawn.ts +130 -0
  72. package/src/agent.ts +96 -587
  73. package/src/execution-mode.ts +43 -0
  74. package/src/gateway-models.ts +135 -0
  75. package/src/index.ts +79 -0
  76. package/src/otel-log-writer.test.ts +105 -0
  77. package/src/otel-log-writer.ts +94 -0
  78. package/src/posthog-api.ts +75 -235
  79. package/src/resume.ts +115 -0
  80. package/src/sagas/apply-snapshot-saga.test.ts +690 -0
  81. package/src/sagas/apply-snapshot-saga.ts +88 -0
  82. package/src/sagas/capture-tree-saga.test.ts +892 -0
  83. package/src/sagas/capture-tree-saga.ts +141 -0
  84. package/src/sagas/resume-saga.test.ts +558 -0
  85. package/src/sagas/resume-saga.ts +332 -0
  86. package/src/sagas/test-fixtures.ts +250 -0
  87. package/src/server/agent-server.test.ts +220 -0
  88. package/src/server/agent-server.ts +748 -0
  89. package/src/server/bin.ts +88 -0
  90. package/src/server/jwt.ts +65 -0
  91. package/src/server/schemas.ts +47 -0
  92. package/src/server/types.ts +13 -0
  93. package/src/server/utils/retry.test.ts +122 -0
  94. package/src/server/utils/retry.ts +61 -0
  95. package/src/server/utils/sse-parser.test.ts +93 -0
  96. package/src/server/utils/sse-parser.ts +46 -0
  97. package/src/session-log-writer.test.ts +140 -0
  98. package/src/session-log-writer.ts +137 -0
  99. package/src/test/assertions.ts +114 -0
  100. package/src/test/controllers/sse-controller.ts +107 -0
  101. package/src/test/fixtures/api.ts +111 -0
  102. package/src/test/fixtures/config.ts +33 -0
  103. package/src/test/fixtures/notifications.ts +92 -0
  104. package/src/test/mocks/claude-sdk.ts +251 -0
  105. package/src/test/mocks/msw-handlers.ts +48 -0
  106. package/src/test/setup.ts +114 -0
  107. package/src/test/wait.ts +41 -0
  108. package/src/tree-tracker.ts +173 -0
  109. package/src/types.ts +54 -137
  110. package/src/utils/acp-content.ts +58 -0
  111. package/src/utils/async-mutex.test.ts +104 -0
  112. package/src/utils/async-mutex.ts +31 -0
  113. package/src/utils/common.ts +15 -0
  114. package/src/utils/gateway.ts +9 -6
  115. package/src/utils/logger.ts +0 -30
  116. package/src/utils/streams.ts +220 -0
  117. package/CLAUDE.md +0 -331
  118. package/src/adapters/claude/claude.ts +0 -1947
  119. package/src/adapters/claude/mcp-server.ts +0 -810
  120. package/src/adapters/claude/utils.ts +0 -267
  121. package/src/adapters/connection.ts +0 -95
  122. package/src/file-manager.ts +0 -273
  123. package/src/git-manager.ts +0 -577
  124. package/src/schemas.ts +0 -241
  125. package/src/session-store.ts +0 -259
  126. package/src/task-manager.ts +0 -163
  127. package/src/todo-manager.ts +0 -180
  128. package/src/tools/registry.ts +0 -134
  129. package/src/tools/types.ts +0 -133
  130. package/src/utils/tapped-stream.ts +0 -60
  131. package/src/worktree-manager.ts +0 -974
package/src/schemas.ts DELETED
@@ -1,241 +0,0 @@
1
- import { z } from "zod";
2
-
3
- // Base event schema with timestamp
4
- const BaseEventSchema = z.object({
5
- ts: z.number(),
6
- });
7
-
8
- // Streaming content events
9
- export const TokenEventSchema = BaseEventSchema.extend({
10
- type: z.literal("token"),
11
- content: z.string(),
12
- contentType: z.enum(["text", "thinking", "tool_input"]).optional(),
13
- });
14
-
15
- export const ContentBlockStartEventSchema = BaseEventSchema.extend({
16
- type: z.literal("content_block_start"),
17
- index: z.number(),
18
- contentType: z.enum(["text", "tool_use", "thinking"]),
19
- toolName: z.string().optional(),
20
- toolId: z.string().optional(),
21
- });
22
-
23
- export const ContentBlockStopEventSchema = BaseEventSchema.extend({
24
- type: z.literal("content_block_stop"),
25
- index: z.number(),
26
- });
27
-
28
- // Tool events
29
- export const ToolCallEventSchema = BaseEventSchema.extend({
30
- type: z.literal("tool_call"),
31
- toolName: z.string(),
32
- callId: z.string(),
33
- args: z.record(z.string(), z.unknown()),
34
- parentToolUseId: z.string().nullable().optional(),
35
- tool: z.unknown().optional(),
36
- category: z.unknown().optional(),
37
- });
38
-
39
- export const ToolResultEventSchema = BaseEventSchema.extend({
40
- type: z.literal("tool_result"),
41
- toolName: z.string(),
42
- callId: z.string(),
43
- result: z.unknown(),
44
- isError: z.boolean().optional(),
45
- parentToolUseId: z.string().nullable().optional(),
46
- tool: z.unknown().optional(),
47
- category: z.unknown().optional(),
48
- });
49
-
50
- // Message lifecycle events
51
- export const MessageStartEventSchema = BaseEventSchema.extend({
52
- type: z.literal("message_start"),
53
- messageId: z.string().optional(),
54
- model: z.string().optional(),
55
- });
56
-
57
- export const MessageDeltaEventSchema = BaseEventSchema.extend({
58
- type: z.literal("message_delta"),
59
- stopReason: z.string().optional(),
60
- stopSequence: z.string().optional(),
61
- usage: z
62
- .object({
63
- outputTokens: z.number(),
64
- })
65
- .optional(),
66
- });
67
-
68
- export const MessageStopEventSchema = BaseEventSchema.extend({
69
- type: z.literal("message_stop"),
70
- });
71
-
72
- // User message events
73
- export const UserMessageEventSchema = BaseEventSchema.extend({
74
- type: z.literal("user_message"),
75
- content: z.string(),
76
- isSynthetic: z.boolean().optional(),
77
- });
78
-
79
- // System events
80
- export const StatusEventSchema = BaseEventSchema.extend({
81
- type: z.literal("status"),
82
- phase: z.string(),
83
- kind: z.string().optional(),
84
- branch: z.string().optional(),
85
- prUrl: z.string().optional(),
86
- taskId: z.string().optional(),
87
- messageId: z.string().optional(),
88
- model: z.string().optional(),
89
- }).passthrough(); // Allow additional fields
90
-
91
- export const InitEventSchema = BaseEventSchema.extend({
92
- type: z.literal("init"),
93
- model: z.string(),
94
- tools: z.array(z.string()),
95
- permissionMode: z.string(),
96
- cwd: z.string(),
97
- apiKeySource: z.string(),
98
- agents: z.array(z.string()).optional(),
99
- slashCommands: z.array(z.string()).optional(),
100
- outputStyle: z.string().optional(),
101
- mcpServers: z
102
- .array(z.object({ name: z.string(), status: z.string() }))
103
- .optional(),
104
- });
105
-
106
- // Console event for log-style output
107
- export const ConsoleEventSchema = BaseEventSchema.extend({
108
- type: z.literal("console"),
109
- level: z.enum(["debug", "info", "warn", "error"]),
110
- message: z.string(),
111
- });
112
-
113
- export const CompactBoundaryEventSchema = BaseEventSchema.extend({
114
- type: z.literal("compact_boundary"),
115
- trigger: z.enum(["manual", "auto"]),
116
- preTokens: z.number(),
117
- });
118
-
119
- // Result events
120
- export const DoneEventSchema = BaseEventSchema.extend({
121
- type: z.literal("done"),
122
- result: z.string().optional(),
123
- durationMs: z.number().optional(),
124
- durationApiMs: z.number().optional(),
125
- numTurns: z.number().optional(),
126
- totalCostUsd: z.number().optional(),
127
- usage: z.unknown().optional(),
128
- modelUsage: z
129
- .record(
130
- z.string(),
131
- z.object({
132
- inputTokens: z.number(),
133
- outputTokens: z.number(),
134
- cacheReadInputTokens: z.number(),
135
- cacheCreationInputTokens: z.number(),
136
- webSearchRequests: z.number(),
137
- costUSD: z.number(),
138
- contextWindow: z.number(),
139
- }),
140
- )
141
- .optional(),
142
- permissionDenials: z
143
- .array(
144
- z.object({
145
- tool_name: z.string(),
146
- tool_use_id: z.string(),
147
- tool_input: z.record(z.string(), z.unknown()),
148
- }),
149
- )
150
- .optional(),
151
- });
152
-
153
- export const ErrorEventSchema = BaseEventSchema.extend({
154
- type: z.literal("error"),
155
- message: z.string(),
156
- error: z.unknown().optional(),
157
- errorType: z.string().optional(),
158
- context: z.record(z.string(), z.unknown()).optional(),
159
- sdkError: z.unknown().optional(),
160
- });
161
-
162
- // Metric and artifact events
163
- export const MetricEventSchema = BaseEventSchema.extend({
164
- type: z.literal("metric"),
165
- key: z.string(),
166
- value: z.number(),
167
- unit: z.string().optional(),
168
- });
169
-
170
- export const ArtifactEventSchema = BaseEventSchema.extend({
171
- type: z.literal("artifact"),
172
- kind: z.string(),
173
- content: z.unknown(),
174
- });
175
-
176
- export const RawSDKEventSchema = BaseEventSchema.extend({
177
- type: z.literal("raw_sdk_event"),
178
- sdkMessage: z.unknown(),
179
- });
180
-
181
- export const AgentEventSchema = z.discriminatedUnion("type", [
182
- TokenEventSchema,
183
- ContentBlockStartEventSchema,
184
- ContentBlockStopEventSchema,
185
- ToolCallEventSchema,
186
- ToolResultEventSchema,
187
- MessageStartEventSchema,
188
- MessageDeltaEventSchema,
189
- MessageStopEventSchema,
190
- UserMessageEventSchema,
191
- StatusEventSchema,
192
- InitEventSchema,
193
- ConsoleEventSchema,
194
- CompactBoundaryEventSchema,
195
- DoneEventSchema,
196
- ErrorEventSchema,
197
- MetricEventSchema,
198
- ArtifactEventSchema,
199
- RawSDKEventSchema,
200
- ]);
201
-
202
- export type TokenEvent = z.infer<typeof TokenEventSchema>;
203
- export type ContentBlockStartEvent = z.infer<
204
- typeof ContentBlockStartEventSchema
205
- >;
206
- export type ContentBlockStopEvent = z.infer<typeof ContentBlockStopEventSchema>;
207
- export type ToolCallEvent = z.infer<typeof ToolCallEventSchema>;
208
- export type ToolResultEvent = z.infer<typeof ToolResultEventSchema>;
209
- export type MessageStartEvent = z.infer<typeof MessageStartEventSchema>;
210
- export type MessageDeltaEvent = z.infer<typeof MessageDeltaEventSchema>;
211
- export type MessageStopEvent = z.infer<typeof MessageStopEventSchema>;
212
- export type UserMessageEvent = z.infer<typeof UserMessageEventSchema>;
213
- export type StatusEvent = z.infer<typeof StatusEventSchema>;
214
- export type InitEvent = z.infer<typeof InitEventSchema>;
215
- export type ConsoleEvent = z.infer<typeof ConsoleEventSchema>;
216
- export type CompactBoundaryEvent = z.infer<typeof CompactBoundaryEventSchema>;
217
- export type DoneEvent = z.infer<typeof DoneEventSchema>;
218
- export type ErrorEvent = z.infer<typeof ErrorEventSchema>;
219
- export type MetricEvent = z.infer<typeof MetricEventSchema>;
220
- export type ArtifactEvent = z.infer<typeof ArtifactEventSchema>;
221
- export type RawSDKEvent = z.infer<typeof RawSDKEventSchema>;
222
- export type AgentEvent = z.infer<typeof AgentEventSchema>;
223
-
224
- /**
225
- * Parse and validate an AgentEvent from unknown input.
226
- * Returns the parsed event if valid, or null if invalid.
227
- */
228
- export function parseAgentEvent(input: unknown): AgentEvent | null {
229
- const result = AgentEventSchema.safeParse(input);
230
- return result.success ? result.data : null;
231
- }
232
-
233
- /**
234
- * Parse and validate multiple AgentEvents from an array of unknown inputs.
235
- * Invalid entries are discarded.
236
- */
237
- export function parseAgentEvents(inputs: unknown[]): AgentEvent[] {
238
- return inputs
239
- .map((input) => parseAgentEvent(input))
240
- .filter((event): event is AgentEvent => event !== null);
241
- }
@@ -1,259 +0,0 @@
1
- import type { PostHogAPIClient, TaskRunUpdate } from "./posthog-api.js";
2
- import type { StoredNotification, TaskRun } from "./types.js";
3
- import { Logger } from "./utils/logger.js";
4
-
5
- export interface SessionPersistenceConfig {
6
- taskId: string;
7
- runId: string;
8
- logUrl: string;
9
- sdkSessionId?: string;
10
- }
11
-
12
- export class SessionStore {
13
- private posthogAPI?: PostHogAPIClient;
14
- private pendingEntries: Map<string, StoredNotification[]> = new Map();
15
- private flushTimeouts: Map<string, NodeJS.Timeout> = new Map();
16
- private configs: Map<string, SessionPersistenceConfig> = new Map();
17
- private logger: Logger;
18
-
19
- constructor(posthogAPI?: PostHogAPIClient, logger?: Logger) {
20
- this.posthogAPI = posthogAPI;
21
- this.logger =
22
- logger ?? new Logger({ debug: false, prefix: "[SessionStore]" });
23
-
24
- // Flush all pending on process exit
25
- const flushAllAndExit = async () => {
26
- const flushPromises: Promise<void>[] = [];
27
- for (const sessionId of this.configs.keys()) {
28
- flushPromises.push(this.flush(sessionId));
29
- }
30
- await Promise.all(flushPromises);
31
- process.exit(0);
32
- };
33
-
34
- process.on("beforeExit", () => {
35
- flushAllAndExit().catch((e) => this.logger.error("Flush failed:", e));
36
- });
37
- process.on("SIGINT", () => {
38
- flushAllAndExit().catch((e) => this.logger.error("Flush failed:", e));
39
- });
40
- process.on("SIGTERM", () => {
41
- flushAllAndExit().catch((e) => this.logger.error("Flush failed:", e));
42
- });
43
- }
44
-
45
- /** Register a session for persistence */
46
- register(sessionId: string, config: SessionPersistenceConfig): void {
47
- this.configs.set(sessionId, config);
48
- }
49
-
50
- /** Unregister and flush pending */
51
- async unregister(sessionId: string): Promise<void> {
52
- await this.flush(sessionId);
53
- this.configs.delete(sessionId);
54
- }
55
-
56
- /** Check if a session is registered for persistence */
57
- isRegistered(sessionId: string): boolean {
58
- return this.configs.has(sessionId);
59
- }
60
-
61
- /**
62
- * Append a raw JSON-RPC line for persistence.
63
- * Parses and wraps as StoredNotification for the API.
64
- */
65
- appendRawLine(sessionId: string, line: string): void {
66
- const config = this.configs.get(sessionId);
67
- if (!config) {
68
- return;
69
- }
70
-
71
- try {
72
- const message = JSON.parse(line);
73
- const entry: StoredNotification = {
74
- type: "notification",
75
- timestamp: new Date().toISOString(),
76
- notification: message,
77
- };
78
-
79
- const pending = this.pendingEntries.get(sessionId) ?? [];
80
- pending.push(entry);
81
- this.pendingEntries.set(sessionId, pending);
82
-
83
- this.scheduleFlush(sessionId);
84
- } catch {
85
- this.logger.warn("Failed to parse raw line for persistence", {
86
- sessionId,
87
- lineLength: line.length,
88
- });
89
- }
90
- }
91
-
92
- /** Load raw JSON-RPC messages from S3 */
93
- async load(logUrl: string): Promise<StoredNotification[]> {
94
- const response = await fetch(logUrl);
95
-
96
- if (!response.ok) {
97
- return [];
98
- }
99
-
100
- const content = await response.text();
101
- if (!content.trim()) return [];
102
-
103
- return content
104
- .trim()
105
- .split("\n")
106
- .map((line) => {
107
- try {
108
- return JSON.parse(line) as StoredNotification;
109
- } catch {
110
- return null;
111
- }
112
- })
113
- .filter((entry): entry is StoredNotification => entry !== null);
114
- }
115
-
116
- /**
117
- * Poll S3 for new entries since last check.
118
- * Used for interrupt handling in cloud mode.
119
- */
120
- async pollForNewEntries(
121
- sessionId: string,
122
- lastKnownCount: number,
123
- ): Promise<StoredNotification[]> {
124
- const config = this.configs.get(sessionId);
125
- if (!config?.logUrl) return [];
126
-
127
- const entries = await this.load(config.logUrl);
128
- return entries.slice(lastKnownCount);
129
- }
130
-
131
- /** Force flush pending entries */
132
- async flush(sessionId: string): Promise<void> {
133
- const config = this.configs.get(sessionId);
134
- const pending = this.pendingEntries.get(sessionId);
135
-
136
- if (!config || !pending?.length) return;
137
-
138
- this.pendingEntries.delete(sessionId);
139
- const timeout = this.flushTimeouts.get(sessionId);
140
- if (timeout) {
141
- clearTimeout(timeout);
142
- this.flushTimeouts.delete(sessionId);
143
- }
144
-
145
- if (!this.posthogAPI) {
146
- this.logger.debug("No PostHog API configured, skipping flush");
147
- return;
148
- }
149
-
150
- try {
151
- await this.posthogAPI.appendTaskRunLog(
152
- config.taskId,
153
- config.runId,
154
- pending,
155
- );
156
- } catch (error) {
157
- this.logger.error("Failed to persist session logs:", error);
158
- }
159
- }
160
-
161
- private scheduleFlush(sessionId: string): void {
162
- const existing = this.flushTimeouts.get(sessionId);
163
- if (existing) clearTimeout(existing);
164
- const timeout = setTimeout(() => this.flush(sessionId), 500);
165
- this.flushTimeouts.set(sessionId, timeout);
166
- }
167
-
168
- /** Get the persistence config for a session */
169
- getConfig(sessionId: string): SessionPersistenceConfig | undefined {
170
- return this.configs.get(sessionId);
171
- }
172
-
173
- /**
174
- * Start a session for persistence.
175
- * Loads the task run and updates status to "in_progress".
176
- */
177
- async start(
178
- sessionId: string,
179
- taskId: string,
180
- runId: string,
181
- ): Promise<TaskRun | undefined> {
182
- if (!this.posthogAPI) {
183
- this.logger.debug(
184
- "No PostHog API configured, registering session without persistence",
185
- );
186
- this.register(sessionId, {
187
- taskId,
188
- runId,
189
- logUrl: "",
190
- });
191
- return undefined;
192
- }
193
-
194
- const taskRun = await this.posthogAPI.getTaskRun(taskId, runId);
195
-
196
- this.register(sessionId, {
197
- taskId,
198
- runId,
199
- logUrl: taskRun.log_url,
200
- });
201
-
202
- await this.updateTaskRun(sessionId, { status: "in_progress" });
203
-
204
- return taskRun;
205
- }
206
-
207
- /**
208
- * Mark a session as completed.
209
- */
210
- async complete(sessionId: string): Promise<void> {
211
- await this.flush(sessionId);
212
- await this.updateTaskRun(sessionId, { status: "completed" });
213
- }
214
-
215
- /**
216
- * Mark a session as failed.
217
- */
218
- async fail(sessionId: string, error: Error | string): Promise<void> {
219
- await this.flush(sessionId);
220
- const message = typeof error === "string" ? error : error.message;
221
- await this.updateTaskRun(sessionId, {
222
- status: "failed",
223
- error_message: message,
224
- });
225
- this.logger.error("Session failed", { sessionId, error: message });
226
- }
227
-
228
- /**
229
- * Update the task run associated with a session.
230
- */
231
- async updateTaskRun(
232
- sessionId: string,
233
- update: TaskRunUpdate,
234
- ): Promise<TaskRun | undefined> {
235
- const config = this.configs.get(sessionId);
236
- if (!config) {
237
- this.logger.error(
238
- `Cannot update task run: session ${sessionId} not registered`,
239
- );
240
- return undefined;
241
- }
242
-
243
- if (!this.posthogAPI) {
244
- this.logger.debug("No PostHog API configured, skipping task run update");
245
- return undefined;
246
- }
247
-
248
- try {
249
- return await this.posthogAPI.updateTaskRun(
250
- config.taskId,
251
- config.runId,
252
- update,
253
- );
254
- } catch (error) {
255
- this.logger.error("Failed to update task run:", error);
256
- return undefined;
257
- }
258
- }
259
- }
@@ -1,163 +0,0 @@
1
- import { randomBytes } from "node:crypto";
2
-
3
- export interface TaskExecutionState {
4
- taskId: string;
5
- status: "running" | "completed" | "failed" | "canceled" | "timeout";
6
- mode: "plan_only" | "plan_and_build" | "build_only";
7
- result?: unknown;
8
- startedAt: number;
9
- completedAt?: number;
10
- abortController?: AbortController;
11
- }
12
-
13
- export class TaskManager {
14
- public executionStates = new Map<string, TaskExecutionState>();
15
- private defaultTimeout = 10 * 60 * 1000; // 10 minutes
16
-
17
- generateExecutionId(): string {
18
- return randomBytes(16).toString("hex");
19
- }
20
-
21
- startExecution(
22
- taskId: string,
23
- mode: "plan_only" | "plan_and_build" | "build_only",
24
- executionId: string = this.generateExecutionId(),
25
- ): TaskExecutionState {
26
- const executionState: TaskExecutionState = {
27
- taskId,
28
- status: "running",
29
- mode,
30
- startedAt: Date.now(),
31
- abortController: new AbortController(),
32
- };
33
-
34
- this.executionStates.set(executionId, executionState);
35
- this.scheduleTimeout(executionId);
36
-
37
- return executionState;
38
- }
39
-
40
- async waitForCompletion(executionId: string): Promise<unknown> {
41
- const execution = this.executionStates.get(executionId);
42
- if (!execution) {
43
- throw new Error(`Execution ${executionId} not found`);
44
- }
45
-
46
- if (execution.result && execution.status === "completed") {
47
- return execution.result;
48
- }
49
-
50
- return new Promise((resolve, reject) => {
51
- const checkInterval = setInterval(() => {
52
- const currentExecution = this.executionStates.get(executionId);
53
- if (!currentExecution) {
54
- clearInterval(checkInterval);
55
- reject(new Error(`Execution ${executionId} disappeared`));
56
- return;
57
- }
58
-
59
- if (
60
- currentExecution.status === "completed" &&
61
- currentExecution.result
62
- ) {
63
- clearInterval(checkInterval);
64
- resolve(currentExecution.result);
65
- } else if (
66
- currentExecution.status === "failed" ||
67
- currentExecution.status === "canceled" ||
68
- currentExecution.status === "timeout"
69
- ) {
70
- clearInterval(checkInterval);
71
- reject(
72
- new Error(`Execution ${executionId} ${currentExecution.status}`),
73
- );
74
- }
75
- }, 100);
76
- });
77
- }
78
-
79
- completeExecution(executionId: string, result: unknown): void {
80
- const execution = this.executionStates.get(executionId);
81
- if (!execution) {
82
- throw new Error(`Execution ${executionId} not found`);
83
- }
84
-
85
- execution.status = "completed";
86
- execution.result = result;
87
- execution.completedAt = Date.now();
88
- }
89
-
90
- failExecution(executionId: string, error: Error): void {
91
- const execution = this.executionStates.get(executionId);
92
- if (!execution) {
93
- throw new Error(`Execution ${executionId} not found`);
94
- }
95
-
96
- execution.status = "failed";
97
- execution.completedAt = Date.now();
98
- execution.result = {
99
- error: error.message,
100
- status: "failed",
101
- };
102
- }
103
-
104
- cancelExecution(executionId: string): void {
105
- const execution = this.executionStates.get(executionId);
106
- if (!execution) {
107
- throw new Error(`Execution ${executionId} not found`);
108
- }
109
-
110
- execution.status = "canceled";
111
- execution.completedAt = Date.now();
112
- execution.abortController?.abort();
113
-
114
- if (!execution.result) {
115
- execution.result = {
116
- status: "canceled",
117
- message: "Execution was canceled",
118
- };
119
- }
120
- }
121
-
122
- getExecution(executionId: string): TaskExecutionState | undefined {
123
- return this.executionStates.get(executionId);
124
- }
125
-
126
- getAbortSignal(executionId: string): AbortSignal | undefined {
127
- return this.executionStates.get(executionId)?.abortController?.signal;
128
- }
129
-
130
- getAbortController(executionId: string): AbortController | undefined {
131
- return this.executionStates.get(executionId)?.abortController;
132
- }
133
-
134
- private scheduleTimeout(
135
- executionId: string,
136
- timeout: number = this.defaultTimeout,
137
- ): void {
138
- setTimeout(() => {
139
- const execution = this.executionStates.get(executionId);
140
- if (execution && execution.status === "running") {
141
- execution.status = "timeout";
142
- execution.completedAt = Date.now();
143
- execution.abortController?.abort();
144
-
145
- if (!execution.result) {
146
- execution.result = {
147
- status: "timeout",
148
- message: "Execution timed out",
149
- };
150
- }
151
- }
152
- }, timeout);
153
- }
154
-
155
- cleanup(olderThan: number = 60 * 60 * 1000): void {
156
- const cutoff = Date.now() - olderThan;
157
- for (const [executionId, execution] of this.executionStates) {
158
- if (execution.completedAt && execution.completedAt < cutoff) {
159
- this.executionStates.delete(executionId);
160
- }
161
- }
162
- }
163
- }