@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
package/src/types.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
// import and export to keep a single type file
|
|
2
|
-
|
|
3
|
-
import type { SessionNotification } from "@agentclientprotocol/sdk";
|
|
4
|
-
import type {
|
|
5
|
-
CanUseTool,
|
|
6
|
-
PermissionResult,
|
|
7
|
-
} from "@anthropic-ai/claude-agent-sdk";
|
|
8
|
-
export type { CanUseTool, PermissionResult, SessionNotification };
|
|
9
|
-
|
|
10
1
|
/**
|
|
11
2
|
* Stored custom notification following ACP extensibility model.
|
|
12
3
|
* Custom notifications use underscore-prefixed methods (e.g., `_posthog/phase_start`).
|
|
@@ -29,7 +20,7 @@ export interface StoredNotification {
|
|
|
29
20
|
*/
|
|
30
21
|
export type StoredEntry = StoredNotification;
|
|
31
22
|
|
|
32
|
-
// PostHog Task model (matches
|
|
23
|
+
// PostHog Task model (matches Twig's OpenAPI schema)
|
|
33
24
|
export interface Task {
|
|
34
25
|
id: string;
|
|
35
26
|
task_number?: number;
|
|
@@ -64,7 +55,8 @@ export type ArtifactType =
|
|
|
64
55
|
| "context"
|
|
65
56
|
| "reference"
|
|
66
57
|
| "output"
|
|
67
|
-
| "artifact"
|
|
58
|
+
| "artifact"
|
|
59
|
+
| "tree_snapshot";
|
|
68
60
|
|
|
69
61
|
export interface TaskRunArtifact {
|
|
70
62
|
name: string;
|
|
@@ -104,85 +96,23 @@ export interface TaskRun {
|
|
|
104
96
|
completed_at: string | null;
|
|
105
97
|
}
|
|
106
98
|
|
|
107
|
-
export interface
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export interface TaskArtifactUploadPayload {
|
|
115
|
-
name: string;
|
|
116
|
-
type: ArtifactType;
|
|
117
|
-
content: string;
|
|
118
|
-
content_type?: string;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export enum PermissionMode {
|
|
122
|
-
PLAN = "plan",
|
|
123
|
-
DEFAULT = "default",
|
|
124
|
-
ACCEPT_EDITS = "acceptEdits",
|
|
125
|
-
BYPASS = "bypassPermissions",
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export interface ExecutionOptions {
|
|
129
|
-
repositoryPath?: string;
|
|
130
|
-
permissionMode?: PermissionMode;
|
|
99
|
+
export interface ProcessSpawnedCallback {
|
|
100
|
+
onProcessSpawned?: (info: {
|
|
101
|
+
pid: number;
|
|
102
|
+
command: string;
|
|
103
|
+
sessionId?: string;
|
|
104
|
+
}) => void;
|
|
105
|
+
onProcessExited?: (pid: number) => void;
|
|
131
106
|
}
|
|
132
107
|
|
|
133
108
|
export interface TaskExecutionOptions {
|
|
134
109
|
repositoryPath?: string;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
queryOverrides?: Record<string, unknown>;
|
|
140
|
-
// Fine-grained permission control (only applied to build phase)
|
|
141
|
-
// See: https://docs.claude.com/en/api/agent-sdk/permissions
|
|
142
|
-
canUseTool?: CanUseTool;
|
|
143
|
-
skipGitBranch?: boolean; // Skip creating a task-specific git branch
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export interface ExecutionResult {
|
|
147
|
-
// biome-ignore lint/suspicious/noExplicitAny: Results array contains varying SDK response types
|
|
148
|
-
results: any[];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export interface PlanResult {
|
|
152
|
-
plan: string;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export interface TaskExecutionResult {
|
|
156
|
-
task: Task;
|
|
157
|
-
plan?: string;
|
|
158
|
-
executionResult?: ExecutionResult;
|
|
110
|
+
adapter?: "claude" | "codex";
|
|
111
|
+
model?: string;
|
|
112
|
+
codexBinaryPath?: string;
|
|
113
|
+
processCallbacks?: ProcessSpawnedCallback;
|
|
159
114
|
}
|
|
160
115
|
|
|
161
|
-
// MCP Server configuration types (re-exported from Claude SDK for convenience)
|
|
162
|
-
export type McpServerConfig =
|
|
163
|
-
| {
|
|
164
|
-
type?: "stdio";
|
|
165
|
-
command: string;
|
|
166
|
-
args?: string[];
|
|
167
|
-
env?: Record<string, string>;
|
|
168
|
-
}
|
|
169
|
-
| {
|
|
170
|
-
type: "sse";
|
|
171
|
-
url: string;
|
|
172
|
-
headers?: Record<string, string>;
|
|
173
|
-
}
|
|
174
|
-
| {
|
|
175
|
-
type: "http";
|
|
176
|
-
url: string;
|
|
177
|
-
headers?: Record<string, string>;
|
|
178
|
-
}
|
|
179
|
-
| {
|
|
180
|
-
type: "sdk";
|
|
181
|
-
name: string;
|
|
182
|
-
// biome-ignore lint/suspicious/noExplicitAny: McpServer instance type from external SDK
|
|
183
|
-
instance?: any;
|
|
184
|
-
};
|
|
185
|
-
|
|
186
116
|
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
187
117
|
|
|
188
118
|
export type OnLogCallback = (
|
|
@@ -192,90 +122,57 @@ export type OnLogCallback = (
|
|
|
192
122
|
data?: unknown,
|
|
193
123
|
) => void;
|
|
194
124
|
|
|
195
|
-
export interface AgentConfig {
|
|
196
|
-
workingDirectory?: string;
|
|
197
|
-
|
|
198
|
-
// PostHog API configuration (optional - enables PostHog integration when provided)
|
|
199
|
-
posthogApiUrl?: string;
|
|
200
|
-
getPosthogApiKey?: () => string;
|
|
201
|
-
posthogProjectId?: number;
|
|
202
|
-
|
|
203
|
-
// PostHog MCP configuration
|
|
204
|
-
posthogMcpUrl?: string;
|
|
205
|
-
|
|
206
|
-
// MCP Server configuration
|
|
207
|
-
// Additional MCP servers (PostHog MCP is always included by default)
|
|
208
|
-
// You can override the PostHog MCP config by providing mcpServers.posthog
|
|
209
|
-
mcpServers?: Record<string, McpServerConfig>;
|
|
210
|
-
|
|
211
|
-
// Logging configuration
|
|
212
|
-
debug?: boolean;
|
|
213
|
-
onLog?: OnLogCallback;
|
|
214
|
-
|
|
215
|
-
// Fine-grained permission control for direct run() calls
|
|
216
|
-
// See: https://docs.claude.com/en/api/agent-sdk/permissions
|
|
217
|
-
canUseTool?: CanUseTool;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
125
|
export interface PostHogAPIConfig {
|
|
221
126
|
apiUrl: string;
|
|
222
127
|
getApiKey: () => string;
|
|
223
128
|
projectId: number;
|
|
224
129
|
}
|
|
225
130
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
export interface PostHogResource {
|
|
235
|
-
type: ResourceType;
|
|
236
|
-
id: string;
|
|
237
|
-
url: string;
|
|
238
|
-
title?: string;
|
|
239
|
-
content: string;
|
|
240
|
-
// biome-ignore lint/suspicious/noExplicitAny: Metadata contains varying resource-specific fields
|
|
241
|
-
metadata?: Record<string, any>;
|
|
131
|
+
export interface OtelTransportConfig {
|
|
132
|
+
/** PostHog ingest host, e.g., "https://us.i.posthog.com" */
|
|
133
|
+
host: string;
|
|
134
|
+
/** Project API key */
|
|
135
|
+
apiKey: string;
|
|
136
|
+
/** Override the logs endpoint path (default: /i/v1/logs) */
|
|
137
|
+
logsPath?: string;
|
|
242
138
|
}
|
|
243
139
|
|
|
244
|
-
export interface
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
140
|
+
export interface AgentConfig {
|
|
141
|
+
posthog?: PostHogAPIConfig;
|
|
142
|
+
/** OTEL transport config for shipping logs to PostHog Logs */
|
|
143
|
+
otelTransport?: OtelTransportConfig;
|
|
144
|
+
debug?: boolean;
|
|
145
|
+
onLog?: OnLogCallback;
|
|
249
146
|
}
|
|
250
147
|
|
|
251
|
-
//
|
|
252
|
-
export interface
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
options: string[];
|
|
148
|
+
// Device info for tracking where work happens
|
|
149
|
+
export interface DeviceInfo {
|
|
150
|
+
type: "local" | "cloud";
|
|
151
|
+
name?: string;
|
|
256
152
|
}
|
|
257
153
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
154
|
+
// Agent execution mode - for tracking interactive vs background runs, when backgrounded an agent will continue working without asking questions
|
|
155
|
+
export type AgentMode = "interactive" | "background";
|
|
156
|
+
|
|
157
|
+
// Git file status codes
|
|
158
|
+
export type FileStatus = "A" | "M" | "D";
|
|
159
|
+
|
|
160
|
+
export interface FileChange {
|
|
161
|
+
path: string;
|
|
162
|
+
status: FileStatus;
|
|
262
163
|
}
|
|
263
164
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
165
|
+
// Tree snapshot - what TreeTracker captures
|
|
166
|
+
export interface TreeSnapshot {
|
|
167
|
+
treeHash: string;
|
|
168
|
+
baseCommit: string | null;
|
|
169
|
+
archiveUrl?: string;
|
|
170
|
+
changes: FileChange[];
|
|
171
|
+
timestamp: string;
|
|
172
|
+
interrupted?: boolean;
|
|
272
173
|
}
|
|
273
174
|
|
|
274
|
-
//
|
|
275
|
-
export interface
|
|
276
|
-
|
|
277
|
-
worktreeName: string;
|
|
278
|
-
branchName: string;
|
|
279
|
-
baseBranch: string;
|
|
280
|
-
createdAt: string;
|
|
175
|
+
// Tree snapshot event - includes device info when sent as notification
|
|
176
|
+
export interface TreeSnapshotEvent extends TreeSnapshot {
|
|
177
|
+
device?: DeviceInfo;
|
|
281
178
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ContentBlock, ToolCallContent } from "@agentclientprotocol/sdk";
|
|
2
|
+
|
|
3
|
+
export function text(value: string): ContentBlock {
|
|
4
|
+
return { type: "text", text: value };
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function image(
|
|
8
|
+
data: string,
|
|
9
|
+
mimeType: string,
|
|
10
|
+
uri?: string,
|
|
11
|
+
): ContentBlock {
|
|
12
|
+
return { type: "image", data, mimeType, uri };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function resourceLink(
|
|
16
|
+
uri: string,
|
|
17
|
+
name: string,
|
|
18
|
+
options?: {
|
|
19
|
+
mimeType?: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
size?: bigint;
|
|
23
|
+
},
|
|
24
|
+
): ContentBlock {
|
|
25
|
+
return {
|
|
26
|
+
type: "resource_link",
|
|
27
|
+
uri,
|
|
28
|
+
name,
|
|
29
|
+
...options,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class ToolContentBuilder {
|
|
34
|
+
private items: ToolCallContent[] = [];
|
|
35
|
+
|
|
36
|
+
text(value: string): this {
|
|
37
|
+
this.items.push({ type: "content", content: text(value) });
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
image(data: string, mimeType: string, uri?: string): this {
|
|
42
|
+
this.items.push({ type: "content", content: image(data, mimeType, uri) });
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
diff(path: string, oldText: string | null, newText: string): this {
|
|
47
|
+
this.items.push({ type: "diff", path, oldText, newText });
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
build(): ToolCallContent[] {
|
|
52
|
+
return this.items;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function toolContent(): ToolContentBuilder {
|
|
57
|
+
return new ToolContentBuilder();
|
|
58
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { AsyncMutex } from "./async-mutex.js";
|
|
3
|
+
|
|
4
|
+
describe("AsyncMutex", () => {
|
|
5
|
+
it("acquires lock when unlocked", async () => {
|
|
6
|
+
const mutex = new AsyncMutex();
|
|
7
|
+
|
|
8
|
+
expect(mutex.isLocked()).toBe(false);
|
|
9
|
+
await mutex.acquire();
|
|
10
|
+
expect(mutex.isLocked()).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("releases lock", async () => {
|
|
14
|
+
const mutex = new AsyncMutex();
|
|
15
|
+
|
|
16
|
+
await mutex.acquire();
|
|
17
|
+
expect(mutex.isLocked()).toBe(true);
|
|
18
|
+
|
|
19
|
+
mutex.release();
|
|
20
|
+
expect(mutex.isLocked()).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("queues concurrent acquires", async () => {
|
|
24
|
+
const mutex = new AsyncMutex();
|
|
25
|
+
const order: number[] = [];
|
|
26
|
+
|
|
27
|
+
await mutex.acquire();
|
|
28
|
+
expect(mutex.queueLength).toBe(0);
|
|
29
|
+
|
|
30
|
+
const promise1 = mutex.acquire().then(() => order.push(1));
|
|
31
|
+
const promise2 = mutex.acquire().then(() => order.push(2));
|
|
32
|
+
|
|
33
|
+
expect(mutex.queueLength).toBe(2);
|
|
34
|
+
|
|
35
|
+
mutex.release();
|
|
36
|
+
await promise1;
|
|
37
|
+
expect(order).toEqual([1]);
|
|
38
|
+
|
|
39
|
+
mutex.release();
|
|
40
|
+
await promise2;
|
|
41
|
+
expect(order).toEqual([1, 2]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("processes queue in FIFO order", async () => {
|
|
45
|
+
const mutex = new AsyncMutex();
|
|
46
|
+
const order: number[] = [];
|
|
47
|
+
|
|
48
|
+
await mutex.acquire();
|
|
49
|
+
|
|
50
|
+
const promises = [1, 2, 3, 4, 5].map((n) =>
|
|
51
|
+
mutex.acquire().then(() => {
|
|
52
|
+
order.push(n);
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
expect(mutex.queueLength).toBe(5);
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < 5; i++) {
|
|
59
|
+
mutex.release();
|
|
60
|
+
await promises[i];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
expect(order).toEqual([1, 2, 3, 4, 5]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("serializes concurrent operations", async () => {
|
|
67
|
+
const mutex = new AsyncMutex();
|
|
68
|
+
const results: string[] = [];
|
|
69
|
+
|
|
70
|
+
const operation = async (id: string, delay: number) => {
|
|
71
|
+
await mutex.acquire();
|
|
72
|
+
try {
|
|
73
|
+
results.push(`${id}-start`);
|
|
74
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
75
|
+
results.push(`${id}-end`);
|
|
76
|
+
} finally {
|
|
77
|
+
mutex.release();
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
await Promise.all([
|
|
82
|
+
operation("a", 10),
|
|
83
|
+
operation("b", 5),
|
|
84
|
+
operation("c", 1),
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
expect(results).toEqual([
|
|
88
|
+
"a-start",
|
|
89
|
+
"a-end",
|
|
90
|
+
"b-start",
|
|
91
|
+
"b-end",
|
|
92
|
+
"c-start",
|
|
93
|
+
"c-end",
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("handles release without acquire gracefully", () => {
|
|
98
|
+
const mutex = new AsyncMutex();
|
|
99
|
+
|
|
100
|
+
expect(mutex.isLocked()).toBe(false);
|
|
101
|
+
mutex.release();
|
|
102
|
+
expect(mutex.isLocked()).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export class AsyncMutex {
|
|
2
|
+
private locked = false;
|
|
3
|
+
private queue: Array<() => void> = [];
|
|
4
|
+
|
|
5
|
+
async acquire(): Promise<void> {
|
|
6
|
+
if (!this.locked) {
|
|
7
|
+
this.locked = true;
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
this.queue.push(resolve);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
release(): void {
|
|
16
|
+
const next = this.queue.shift();
|
|
17
|
+
if (next) {
|
|
18
|
+
next();
|
|
19
|
+
} else {
|
|
20
|
+
this.locked = false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
isLocked(): boolean {
|
|
25
|
+
return this.locked;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get queueLength(): number {
|
|
29
|
+
return this.queue.length;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Logger } from "./logger.js";
|
|
2
|
+
|
|
3
|
+
export const IS_ROOT =
|
|
4
|
+
typeof process !== "undefined" &&
|
|
5
|
+
(process.geteuid?.() ?? process.getuid?.()) === 0;
|
|
6
|
+
|
|
7
|
+
export function unreachable(value: never, logger: Logger): void {
|
|
8
|
+
let valueAsString: string;
|
|
9
|
+
try {
|
|
10
|
+
valueAsString = JSON.stringify(value);
|
|
11
|
+
} catch {
|
|
12
|
+
valueAsString = value;
|
|
13
|
+
}
|
|
14
|
+
logger.error(`Unexpected case: ${valueAsString}`);
|
|
15
|
+
}
|
package/src/utils/gateway.ts
CHANGED
|
@@ -2,14 +2,17 @@ export function getLlmGatewayUrl(posthogHost: string): string {
|
|
|
2
2
|
const url = new URL(posthogHost);
|
|
3
3
|
const hostname = url.hostname;
|
|
4
4
|
|
|
5
|
+
// Local development (normalize 127.0.0.1 to localhost)
|
|
5
6
|
if (hostname === "localhost" || hostname === "127.0.0.1") {
|
|
6
|
-
return `${url.protocol}//localhost:3308`;
|
|
7
|
+
return `${url.protocol}//localhost:3308/twig`;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
// Docker containers accessing host
|
|
11
|
+
if (hostname === "host.docker.internal") {
|
|
12
|
+
return `${url.protocol}//host.docker.internal:3308/twig`;
|
|
13
|
+
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
// Production - extract region from hostname, default to US
|
|
16
|
+
const region = hostname.match(/^(us|eu)\.posthog\.com$/)?.[1] ?? "us";
|
|
17
|
+
return `https://gateway.${region}.posthog.com/twig`;
|
|
15
18
|
}
|
package/src/utils/logger.ts
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
import type { LogLevel as LogLevelType, OnLogCallback } from "../types.js";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Simple logger utility with configurable debug mode and external log forwarding
|
|
5
|
-
*/
|
|
6
|
-
export enum LogLevel {
|
|
7
|
-
ERROR = 0,
|
|
8
|
-
WARN = 1,
|
|
9
|
-
INFO = 2,
|
|
10
|
-
DEBUG = 3,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
3
|
export interface LoggerConfig {
|
|
14
4
|
debug?: boolean;
|
|
15
5
|
prefix?: string;
|
|
@@ -30,14 +20,6 @@ export class Logger {
|
|
|
30
20
|
this.onLog = config.onLog;
|
|
31
21
|
}
|
|
32
22
|
|
|
33
|
-
setDebug(enabled: boolean) {
|
|
34
|
-
this.debugEnabled = enabled;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
setOnLog(onLog: OnLogCallback | undefined) {
|
|
38
|
-
this.onLog = onLog;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
23
|
private formatMessage(
|
|
42
24
|
level: string,
|
|
43
25
|
message: string,
|
|
@@ -87,18 +69,6 @@ export class Logger {
|
|
|
87
69
|
this.emitLog("debug", message, data);
|
|
88
70
|
}
|
|
89
71
|
|
|
90
|
-
log(level: LogLevelType, message: string, data?: unknown, scope?: string) {
|
|
91
|
-
const originalScope = this.scope;
|
|
92
|
-
if (scope) {
|
|
93
|
-
this.scope = scope;
|
|
94
|
-
}
|
|
95
|
-
this.emitLog(level, message, data);
|
|
96
|
-
this.scope = originalScope;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Create a child logger with additional prefix and scope
|
|
101
|
-
*/
|
|
102
72
|
child(childPrefix: string): Logger {
|
|
103
73
|
return new Logger({
|
|
104
74
|
debug: this.debugEnabled,
|