@posthog/agent 1.30.0 → 2.0.1
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 +172 -1203
- package/dist/index.js +3704 -6826
- 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 +4451 -0
- package/dist/server/agent-server.js.map +1 -0
- package/dist/server/bin.d.ts +1 -0
- package/dist/server/bin.js +4507 -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 +66 -14
- package/src/acp-extensions.ts +93 -61
- 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 -611
- package/src/adapters/claude/types.ts +61 -0
- package/src/adapters/codex/spawn.ts +130 -0
- package/src/agent.ts +97 -734
- 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 +51 -154
- 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/dist/templates/plan-template.md +0 -41
- package/src/adapters/claude/claude.ts +0 -1543
- package/src/adapters/claude/mcp-server.ts +0 -810
- package/src/adapters/claude/utils.ts +0 -267
- package/src/agents/execution.ts +0 -37
- package/src/agents/planning.ts +0 -60
- package/src/agents/research.ts +0 -160
- package/src/file-manager.ts +0 -306
- package/src/git-manager.ts +0 -577
- package/src/prompt-builder.ts +0 -499
- package/src/schemas.ts +0 -241
- package/src/session-store.ts +0 -259
- package/src/task-manager.ts +0 -163
- package/src/template-manager.ts +0 -236
- package/src/templates/plan-template.md +0 -41
- package/src/todo-manager.ts +0 -180
- package/src/tools/registry.ts +0 -129
- package/src/tools/types.ts +0 -127
- package/src/utils/tapped-stream.ts +0 -60
- package/src/workflow/config.ts +0 -53
- package/src/workflow/steps/build.ts +0 -135
- package/src/workflow/steps/finalize.ts +0 -241
- package/src/workflow/steps/plan.ts +0 -167
- package/src/workflow/steps/research.ts +0 -223
- package/src/workflow/types.ts +0 -62
- package/src/workflow/utils.ts +0 -53
- package/src/worktree-manager.ts +0 -928
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type OtelLogConfig,
|
|
3
|
+
OtelLogWriter,
|
|
4
|
+
type SessionContext,
|
|
5
|
+
} from "./otel-log-writer.js";
|
|
6
|
+
import type { PostHogAPIClient } from "./posthog-api.js";
|
|
7
|
+
import type { StoredNotification } from "./types.js";
|
|
8
|
+
import { Logger } from "./utils/logger.js";
|
|
9
|
+
|
|
10
|
+
export interface SessionLogWriterOptions {
|
|
11
|
+
/** OTEL config for creating writers per session */
|
|
12
|
+
otelConfig?: OtelLogConfig;
|
|
13
|
+
/** PostHog API client for S3 log persistence */
|
|
14
|
+
posthogAPI?: PostHogAPIClient;
|
|
15
|
+
/** Logger instance */
|
|
16
|
+
logger?: Logger;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface SessionState {
|
|
20
|
+
context: SessionContext;
|
|
21
|
+
otelWriter?: OtelLogWriter;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class SessionLogWriter {
|
|
25
|
+
private posthogAPI?: PostHogAPIClient;
|
|
26
|
+
private otelConfig?: OtelLogConfig;
|
|
27
|
+
private pendingEntries: Map<string, StoredNotification[]> = new Map();
|
|
28
|
+
private flushTimeouts: Map<string, NodeJS.Timeout> = new Map();
|
|
29
|
+
private sessions: Map<string, SessionState> = new Map();
|
|
30
|
+
private logger: Logger;
|
|
31
|
+
|
|
32
|
+
constructor(options: SessionLogWriterOptions = {}) {
|
|
33
|
+
this.posthogAPI = options.posthogAPI;
|
|
34
|
+
this.otelConfig = options.otelConfig;
|
|
35
|
+
this.logger =
|
|
36
|
+
options.logger ??
|
|
37
|
+
new Logger({ debug: false, prefix: "[SessionLogWriter]" });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async flushAll(): Promise<void> {
|
|
41
|
+
const flushPromises: Promise<void>[] = [];
|
|
42
|
+
for (const sessionId of this.sessions.keys()) {
|
|
43
|
+
flushPromises.push(this.flush(sessionId));
|
|
44
|
+
}
|
|
45
|
+
await Promise.all(flushPromises);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
register(sessionId: string, context: SessionContext): void {
|
|
49
|
+
if (this.sessions.has(sessionId)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let otelWriter: OtelLogWriter | undefined;
|
|
54
|
+
if (this.otelConfig) {
|
|
55
|
+
// Create a dedicated OtelLogWriter for this session with resource attributes
|
|
56
|
+
otelWriter = new OtelLogWriter(
|
|
57
|
+
this.otelConfig,
|
|
58
|
+
context,
|
|
59
|
+
this.logger.child(`OtelWriter:${sessionId}`),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.sessions.set(sessionId, { context, otelWriter });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
isRegistered(sessionId: string): boolean {
|
|
67
|
+
return this.sessions.has(sessionId);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
appendRawLine(sessionId: string, line: string): void {
|
|
71
|
+
const session = this.sessions.get(sessionId);
|
|
72
|
+
if (!session) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const message = JSON.parse(line);
|
|
78
|
+
const entry: StoredNotification = {
|
|
79
|
+
type: "notification",
|
|
80
|
+
timestamp: new Date().toISOString(),
|
|
81
|
+
notification: message,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (session.otelWriter) {
|
|
85
|
+
session.otelWriter.emit({ notification: entry });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (this.posthogAPI) {
|
|
89
|
+
const pending = this.pendingEntries.get(sessionId) ?? [];
|
|
90
|
+
pending.push(entry);
|
|
91
|
+
this.pendingEntries.set(sessionId, pending);
|
|
92
|
+
this.scheduleFlush(sessionId);
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
this.logger.warn("Failed to parse raw line for persistence", {
|
|
96
|
+
sessionId,
|
|
97
|
+
lineLength: line.length,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async flush(sessionId: string): Promise<void> {
|
|
103
|
+
const session = this.sessions.get(sessionId);
|
|
104
|
+
if (!session) return;
|
|
105
|
+
|
|
106
|
+
if (session.otelWriter) {
|
|
107
|
+
await session.otelWriter.flush();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const pending = this.pendingEntries.get(sessionId);
|
|
111
|
+
if (!this.posthogAPI || !pending?.length) return;
|
|
112
|
+
|
|
113
|
+
this.pendingEntries.delete(sessionId);
|
|
114
|
+
const timeout = this.flushTimeouts.get(sessionId);
|
|
115
|
+
if (timeout) {
|
|
116
|
+
clearTimeout(timeout);
|
|
117
|
+
this.flushTimeouts.delete(sessionId);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
await this.posthogAPI.appendTaskRunLog(
|
|
122
|
+
session.context.taskId,
|
|
123
|
+
session.context.runId,
|
|
124
|
+
pending,
|
|
125
|
+
);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
this.logger.error("Failed to persist session logs:", error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private scheduleFlush(sessionId: string): void {
|
|
132
|
+
const existing = this.flushTimeouts.get(sessionId);
|
|
133
|
+
if (existing) clearTimeout(existing);
|
|
134
|
+
const timeout = setTimeout(() => this.flush(sessionId), 500);
|
|
135
|
+
this.flushTimeouts.set(sessionId, timeout);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
export interface NotificationEntry {
|
|
4
|
+
notification?: {
|
|
5
|
+
method?: string;
|
|
6
|
+
params?: {
|
|
7
|
+
sessionId?: string;
|
|
8
|
+
update?: {
|
|
9
|
+
sessionUpdate?: string;
|
|
10
|
+
content?: {
|
|
11
|
+
type?: string;
|
|
12
|
+
text?: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface NotificationMatcher {
|
|
20
|
+
method?: string;
|
|
21
|
+
text?: string;
|
|
22
|
+
sessionId?: string;
|
|
23
|
+
sessionUpdate?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function entryMatchesNotification(
|
|
27
|
+
entry: unknown,
|
|
28
|
+
matcher: NotificationMatcher,
|
|
29
|
+
): boolean {
|
|
30
|
+
const notification = (entry as NotificationEntry).notification;
|
|
31
|
+
if (!notification) return false;
|
|
32
|
+
|
|
33
|
+
if (matcher.method && notification.method !== matcher.method) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (
|
|
37
|
+
matcher.sessionId &&
|
|
38
|
+
notification.params?.sessionId !== matcher.sessionId
|
|
39
|
+
) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
if (
|
|
43
|
+
matcher.sessionUpdate &&
|
|
44
|
+
notification.params?.update?.sessionUpdate !== matcher.sessionUpdate
|
|
45
|
+
) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (matcher.text) {
|
|
49
|
+
const text = notification.params?.update?.content?.text;
|
|
50
|
+
if (!text || !text.includes(matcher.text)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function findNotification(
|
|
58
|
+
appendLogCalls: unknown[][],
|
|
59
|
+
matcher: NotificationMatcher,
|
|
60
|
+
): NotificationEntry | undefined {
|
|
61
|
+
for (const entries of appendLogCalls) {
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
if (entryMatchesNotification(entry, matcher)) {
|
|
64
|
+
return entry as NotificationEntry;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function hasNotification(
|
|
72
|
+
appendLogCalls: unknown[][],
|
|
73
|
+
matcher: NotificationMatcher,
|
|
74
|
+
): boolean {
|
|
75
|
+
return findNotification(appendLogCalls, matcher) !== undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function expectNotification(
|
|
79
|
+
appendLogCalls: unknown[][],
|
|
80
|
+
matcher: NotificationMatcher,
|
|
81
|
+
): NotificationEntry {
|
|
82
|
+
const found = findNotification(appendLogCalls, matcher);
|
|
83
|
+
expect(
|
|
84
|
+
found,
|
|
85
|
+
`Expected notification matching ${JSON.stringify(matcher)}`,
|
|
86
|
+
).toBeDefined();
|
|
87
|
+
return found!;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function expectNoNotification(
|
|
91
|
+
appendLogCalls: unknown[][],
|
|
92
|
+
matcher: NotificationMatcher,
|
|
93
|
+
): void {
|
|
94
|
+
const found = findNotification(appendLogCalls, matcher);
|
|
95
|
+
expect(
|
|
96
|
+
found,
|
|
97
|
+
`Expected no notification matching ${JSON.stringify(matcher)}`,
|
|
98
|
+
).toBeUndefined();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function countNotifications(
|
|
102
|
+
appendLogCalls: unknown[][],
|
|
103
|
+
matcher: NotificationMatcher,
|
|
104
|
+
): number {
|
|
105
|
+
let count = 0;
|
|
106
|
+
for (const entries of appendLogCalls) {
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
if (entryMatchesNotification(entry, matcher)) {
|
|
109
|
+
count++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return count;
|
|
114
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
type SseState = "idle" | "streaming" | "closing" | "closed";
|
|
2
|
+
|
|
3
|
+
export interface EventOptions {
|
|
4
|
+
id?: string;
|
|
5
|
+
event?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class SseController {
|
|
9
|
+
private encoder = new TextEncoder();
|
|
10
|
+
private controller: ReadableStreamDefaultController<Uint8Array> | null = null;
|
|
11
|
+
private state: SseState = "idle";
|
|
12
|
+
|
|
13
|
+
createStream(): ReadableStream<Uint8Array> {
|
|
14
|
+
return new ReadableStream({
|
|
15
|
+
start: (controller) => {
|
|
16
|
+
this.controller = controller;
|
|
17
|
+
this.state = "streaming";
|
|
18
|
+
},
|
|
19
|
+
cancel: () => {
|
|
20
|
+
this.state = "closed";
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
sendEvent(data: unknown, options: EventOptions = {}): boolean {
|
|
26
|
+
if (
|
|
27
|
+
this.state === "closed" ||
|
|
28
|
+
this.state === "closing" ||
|
|
29
|
+
!this.controller
|
|
30
|
+
) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const lines: string[] = [];
|
|
35
|
+
if (options.id) {
|
|
36
|
+
lines.push(`id: ${options.id}`);
|
|
37
|
+
}
|
|
38
|
+
if (options.event) {
|
|
39
|
+
lines.push(`event: ${options.event}`);
|
|
40
|
+
}
|
|
41
|
+
lines.push(`data: ${JSON.stringify(data)}`);
|
|
42
|
+
lines.push("");
|
|
43
|
+
lines.push("");
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
this.controller.enqueue(this.encoder.encode(lines.join("\n")));
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
this.state = "closed";
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
sendRaw(rawData: string): boolean {
|
|
55
|
+
if (
|
|
56
|
+
this.state === "closed" ||
|
|
57
|
+
this.state === "closing" ||
|
|
58
|
+
!this.controller
|
|
59
|
+
) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
this.controller.enqueue(this.encoder.encode(rawData));
|
|
64
|
+
return true;
|
|
65
|
+
} catch {
|
|
66
|
+
this.state = "closed";
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
sendPartial(partialData: string): boolean {
|
|
72
|
+
return this.sendRaw(partialData);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
error(err: Error): void {
|
|
76
|
+
if (this.state !== "streaming" || !this.controller) return;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
this.state = "closed";
|
|
80
|
+
this.controller.error(err);
|
|
81
|
+
} catch {
|
|
82
|
+
// Already errored or closed
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
close(): void {
|
|
87
|
+
if (this.state === "closed") return;
|
|
88
|
+
|
|
89
|
+
this.state = "closing";
|
|
90
|
+
if (this.controller) {
|
|
91
|
+
try {
|
|
92
|
+
this.controller.close();
|
|
93
|
+
} catch {
|
|
94
|
+
// Already closed
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
this.state = "closed";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
get closed(): boolean {
|
|
101
|
+
return this.state === "closed";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
get currentState(): SseState {
|
|
105
|
+
return this.state;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import { vi } from "vitest";
|
|
8
|
+
import type { PostHogAPIClient } from "../../posthog-api.js";
|
|
9
|
+
import type { TaskRun, TreeSnapshot } from "../../types.js";
|
|
10
|
+
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
12
|
+
|
|
13
|
+
export interface TestRepo {
|
|
14
|
+
path: string;
|
|
15
|
+
cleanup: () => Promise<void>;
|
|
16
|
+
git: (args: string[]) => Promise<string>;
|
|
17
|
+
writeFile: (relativePath: string, content: string) => Promise<void>;
|
|
18
|
+
readFile: (relativePath: string) => Promise<string>;
|
|
19
|
+
deleteFile: (relativePath: string) => Promise<void>;
|
|
20
|
+
exists: (relativePath: string) => boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function createTestRepo(prefix = "test-repo"): Promise<TestRepo> {
|
|
24
|
+
const repoPath = join(
|
|
25
|
+
tmpdir(),
|
|
26
|
+
`${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
27
|
+
);
|
|
28
|
+
await mkdir(repoPath, { recursive: true });
|
|
29
|
+
|
|
30
|
+
const git = async (args: string[]): Promise<string> => {
|
|
31
|
+
const { stdout } = await execFileAsync("git", args, { cwd: repoPath });
|
|
32
|
+
return stdout.trim();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
await git(["init"]);
|
|
36
|
+
await git(["config", "user.email", "test@test.com"]);
|
|
37
|
+
await git(["config", "user.name", "Test"]);
|
|
38
|
+
|
|
39
|
+
await writeFile(join(repoPath, ".gitignore"), ".posthog/\n");
|
|
40
|
+
await writeFile(join(repoPath, "README.md"), "# Test Repo");
|
|
41
|
+
await git(["add", "."]);
|
|
42
|
+
await git(["commit", "-m", "Initial commit"]);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
path: repoPath,
|
|
46
|
+
cleanup: () => rm(repoPath, { recursive: true, force: true }),
|
|
47
|
+
git,
|
|
48
|
+
writeFile: async (relativePath: string, content: string) => {
|
|
49
|
+
const fullPath = join(repoPath, relativePath);
|
|
50
|
+
const dir = join(fullPath, "..");
|
|
51
|
+
await mkdir(dir, { recursive: true });
|
|
52
|
+
await writeFile(fullPath, content);
|
|
53
|
+
},
|
|
54
|
+
readFile: async (relativePath: string) => {
|
|
55
|
+
return readFile(join(repoPath, relativePath), "utf-8");
|
|
56
|
+
},
|
|
57
|
+
deleteFile: async (relativePath: string) => {
|
|
58
|
+
await rm(join(repoPath, relativePath), { force: true });
|
|
59
|
+
},
|
|
60
|
+
exists: (relativePath: string) => {
|
|
61
|
+
return existsSync(join(repoPath, relativePath));
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function createMockApiClient(
|
|
67
|
+
overrides: Partial<PostHogAPIClient> = {},
|
|
68
|
+
): PostHogAPIClient {
|
|
69
|
+
return {
|
|
70
|
+
uploadTaskArtifacts: vi
|
|
71
|
+
.fn()
|
|
72
|
+
.mockResolvedValue([{ storage_path: "gs://bucket/trees/test.tar.gz" }]),
|
|
73
|
+
downloadArtifact: vi.fn(),
|
|
74
|
+
getTaskRun: vi.fn(),
|
|
75
|
+
fetchTaskRunLogs: vi.fn(),
|
|
76
|
+
...overrides,
|
|
77
|
+
} as unknown as PostHogAPIClient;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function createTaskRun(overrides: Partial<TaskRun> = {}): TaskRun {
|
|
81
|
+
return {
|
|
82
|
+
id: "run-1",
|
|
83
|
+
task: "task-1",
|
|
84
|
+
team: 1,
|
|
85
|
+
branch: null,
|
|
86
|
+
stage: null,
|
|
87
|
+
environment: "local",
|
|
88
|
+
status: "in_progress",
|
|
89
|
+
log_url: "https://logs.example.com/run-1",
|
|
90
|
+
error_message: null,
|
|
91
|
+
output: null,
|
|
92
|
+
state: {},
|
|
93
|
+
created_at: new Date().toISOString(),
|
|
94
|
+
updated_at: new Date().toISOString(),
|
|
95
|
+
completed_at: null,
|
|
96
|
+
...overrides,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function createSnapshot(
|
|
101
|
+
overrides: Partial<TreeSnapshot> = {},
|
|
102
|
+
): TreeSnapshot {
|
|
103
|
+
return {
|
|
104
|
+
treeHash: "test-tree-hash",
|
|
105
|
+
baseCommit: null,
|
|
106
|
+
archiveUrl: "gs://bucket/trees/test.tar.gz",
|
|
107
|
+
changes: [],
|
|
108
|
+
timestamp: new Date().toISOString(),
|
|
109
|
+
...overrides,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AgentServerConfig } from "../../server/types.js";
|
|
2
|
+
import type { TestRepo } from "./api.js";
|
|
3
|
+
|
|
4
|
+
export type { AgentServerConfig };
|
|
5
|
+
|
|
6
|
+
// Test RSA public key (for testing only - matches TEST_PRIVATE_KEY in agent-server.test.ts)
|
|
7
|
+
export const TEST_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
8
|
+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6ofeEmDBbLxuAgqPQUho
|
|
9
|
+
7T69vzsc7jRq+NTuPgDJA0EXfaSjuPQ4UwOFc8Jzr6x/MuHiPTfDkJ3uwcKXaKLk
|
|
10
|
+
p+A6AwEv290lH4o/0aBVEsmYk0KFs9B+qNlbrn4s9B3/gc5WRFZ4UkNa7r6kn/uJ
|
|
11
|
+
fHFoHjF2hV4HQ+ZEPBo70ebqisbzthJ79YTCUSnjjhBoAnqf9HOkpDFE10ngvlY8
|
|
12
|
+
qVYPfvMj8bSKTkO1yr/u3vzwNIpanoUUIeH6PQQFo1Ftfh527bIyQI43754MyI6W
|
|
13
|
+
o7kFcjIuxu/b/Dr4o4SzCYyQYd03W1SH4vkZFY/x/eFFHylkXyQNHi8pAFb04hX9
|
|
14
|
+
JwIDAQAB
|
|
15
|
+
-----END PUBLIC KEY-----`;
|
|
16
|
+
|
|
17
|
+
export function createAgentServerConfig(
|
|
18
|
+
repo: TestRepo,
|
|
19
|
+
overrides: Partial<AgentServerConfig> = {},
|
|
20
|
+
): AgentServerConfig {
|
|
21
|
+
return {
|
|
22
|
+
port: 3001,
|
|
23
|
+
repositoryPath: repo.path,
|
|
24
|
+
apiUrl: "http://localhost:8000",
|
|
25
|
+
apiKey: "test-api-key",
|
|
26
|
+
projectId: 1,
|
|
27
|
+
jwtPublicKey: TEST_PUBLIC_KEY,
|
|
28
|
+
mode: "interactive",
|
|
29
|
+
taskId: "test-task-id",
|
|
30
|
+
runId: "test-run-id",
|
|
31
|
+
...overrides,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { POSTHOG_NOTIFICATIONS } from "../../acp-extensions.js";
|
|
2
|
+
import type { StoredNotification } from "../../types.js";
|
|
3
|
+
|
|
4
|
+
export function createNotification(
|
|
5
|
+
method: string,
|
|
6
|
+
params: Record<string, unknown>,
|
|
7
|
+
): StoredNotification {
|
|
8
|
+
return {
|
|
9
|
+
type: "notification",
|
|
10
|
+
timestamp: new Date().toISOString(),
|
|
11
|
+
notification: {
|
|
12
|
+
jsonrpc: "2.0",
|
|
13
|
+
method,
|
|
14
|
+
params,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createUserMessage(content: string): StoredNotification {
|
|
20
|
+
return createNotification("session/update", {
|
|
21
|
+
update: {
|
|
22
|
+
sessionUpdate: "user_message",
|
|
23
|
+
content: { type: "text", text: content },
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createAgentChunk(text: string): StoredNotification {
|
|
29
|
+
return createNotification("session/update", {
|
|
30
|
+
update: {
|
|
31
|
+
sessionUpdate: "agent_message_chunk",
|
|
32
|
+
content: { type: "text", text },
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createToolCall(
|
|
38
|
+
toolCallId: string,
|
|
39
|
+
toolName: string,
|
|
40
|
+
toolInput: unknown,
|
|
41
|
+
): StoredNotification {
|
|
42
|
+
return createNotification("session/update", {
|
|
43
|
+
update: {
|
|
44
|
+
sessionUpdate: "tool_call",
|
|
45
|
+
_meta: {
|
|
46
|
+
claudeCode: { toolCallId, toolName, toolInput },
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createToolResult(
|
|
53
|
+
toolCallId: string,
|
|
54
|
+
toolResponse: unknown,
|
|
55
|
+
): StoredNotification {
|
|
56
|
+
return createNotification("session/update", {
|
|
57
|
+
update: {
|
|
58
|
+
sessionUpdate: "tool_result",
|
|
59
|
+
_meta: {
|
|
60
|
+
claudeCode: { toolCallId, toolResponse },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function createTreeSnapshotNotification(
|
|
67
|
+
treeHash: string,
|
|
68
|
+
archiveUrl?: string,
|
|
69
|
+
options: { interrupted?: boolean; device?: { type: "local" | "cloud" } } = {},
|
|
70
|
+
): StoredNotification {
|
|
71
|
+
return createNotification(POSTHOG_NOTIFICATIONS.TREE_SNAPSHOT, {
|
|
72
|
+
treeHash,
|
|
73
|
+
baseCommit: "abc123",
|
|
74
|
+
archiveUrl,
|
|
75
|
+
changes: [{ path: "file.ts", status: "A" }],
|
|
76
|
+
timestamp: new Date().toISOString(),
|
|
77
|
+
...options,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function createStatusNotification(
|
|
82
|
+
status: "connected" | "disconnected" | "error",
|
|
83
|
+
message?: string,
|
|
84
|
+
): StoredNotification {
|
|
85
|
+
return createNotification("session/update", {
|
|
86
|
+
update: {
|
|
87
|
+
sessionUpdate: "status",
|
|
88
|
+
status,
|
|
89
|
+
message,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|