@posthog/agent 2.0.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.
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 +3252 -5074
  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 +4451 -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 +4507 -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 +66 -14
  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
@@ -0,0 +1,43 @@
1
+ import { IS_ROOT } from "./utils/common.js";
2
+
3
+ export interface ModeInfo {
4
+ id: TwigExecutionMode;
5
+ name: string;
6
+ description: string;
7
+ }
8
+
9
+ const MODES: ModeInfo[] = [
10
+ {
11
+ id: "default",
12
+ name: "Always Ask",
13
+ description: "Prompts for permission on first use of each tool",
14
+ },
15
+ {
16
+ id: "acceptEdits",
17
+ name: "Accept Edits",
18
+ description: "Automatically accepts file edit permissions for the session",
19
+ },
20
+ {
21
+ id: "plan",
22
+ name: "Plan Mode",
23
+ description: "Claude can analyze but not modify files or execute commands",
24
+ },
25
+ {
26
+ id: "bypassPermissions",
27
+ name: "Bypass Permissions",
28
+ description: "Skips all permission prompts",
29
+ },
30
+ ];
31
+
32
+ export const TWIG_EXECUTION_MODES = [
33
+ "default",
34
+ "acceptEdits",
35
+ "plan",
36
+ "bypassPermissions",
37
+ ] as const;
38
+
39
+ export type TwigExecutionMode = (typeof TWIG_EXECUTION_MODES)[number];
40
+
41
+ export function getAvailableModes(): ModeInfo[] {
42
+ return IS_ROOT ? MODES.filter((m) => m.id !== "bypassPermissions") : MODES;
43
+ }
@@ -0,0 +1,135 @@
1
+ export interface GatewayModel {
2
+ id: string;
3
+ owned_by: string;
4
+ context_window: number;
5
+ supports_streaming: boolean;
6
+ supports_vision: boolean;
7
+ }
8
+
9
+ interface GatewayModelsResponse {
10
+ object: "list";
11
+ data: GatewayModel[];
12
+ }
13
+
14
+ export interface FetchGatewayModelsOptions {
15
+ gatewayUrl: string;
16
+ }
17
+
18
+ export const DEFAULT_GATEWAY_MODEL = "claude-opus-4-6";
19
+
20
+ export const BLOCKED_MODELS = new Set(["gpt-5-mini", "openai/gpt-5-mini"]);
21
+
22
+ type ArrayModelsResponse =
23
+ | {
24
+ data?: Array<{ id?: string; owned_by?: string }>;
25
+ models?: Array<{ id?: string; owned_by?: string }>;
26
+ }
27
+ | Array<{ id?: string; owned_by?: string }>;
28
+
29
+ export async function fetchGatewayModels(
30
+ options?: FetchGatewayModelsOptions,
31
+ ): Promise<GatewayModel[]> {
32
+ const gatewayUrl = options?.gatewayUrl ?? process.env.ANTHROPIC_BASE_URL;
33
+ if (!gatewayUrl) {
34
+ return [];
35
+ }
36
+
37
+ const modelsUrl = `${gatewayUrl}/v1/models`;
38
+
39
+ try {
40
+ const response = await fetch(modelsUrl);
41
+
42
+ if (!response.ok) {
43
+ return [];
44
+ }
45
+
46
+ const data = (await response.json()) as GatewayModelsResponse;
47
+ const models = data.data ?? [];
48
+ return models.filter((m) => !BLOCKED_MODELS.has(m.id));
49
+ } catch {
50
+ return [];
51
+ }
52
+ }
53
+
54
+ export function isAnthropicModel(model: GatewayModel): boolean {
55
+ if (model.owned_by) {
56
+ return model.owned_by === "anthropic";
57
+ }
58
+ return model.id.startsWith("claude-") || model.id.startsWith("anthropic/");
59
+ }
60
+
61
+ export async function fetchArrayModelIds(
62
+ options?: FetchGatewayModelsOptions,
63
+ ): Promise<string[]> {
64
+ const models = await fetchArrayModels(options);
65
+ return models.map((model) => model.id);
66
+ }
67
+
68
+ export interface ArrayModelInfo {
69
+ id: string;
70
+ owned_by?: string;
71
+ }
72
+
73
+ export async function fetchArrayModels(
74
+ options?: FetchGatewayModelsOptions,
75
+ ): Promise<ArrayModelInfo[]> {
76
+ const gatewayUrl = options?.gatewayUrl ?? process.env.ANTHROPIC_BASE_URL;
77
+ if (!gatewayUrl) {
78
+ return [];
79
+ }
80
+
81
+ try {
82
+ const base = new URL(gatewayUrl);
83
+ base.pathname = "/array/v1/models";
84
+ base.search = "";
85
+ base.hash = "";
86
+ const response = await fetch(base.toString());
87
+ if (!response.ok) {
88
+ return [];
89
+ }
90
+ const data = (await response.json()) as ArrayModelsResponse;
91
+ const models = Array.isArray(data)
92
+ ? data
93
+ : (data.data ?? data.models ?? []);
94
+ const results: ArrayModelInfo[] = [];
95
+ for (const model of models) {
96
+ const id = model?.id ? String(model.id) : "";
97
+ if (!id) continue;
98
+ results.push({ id, owned_by: model?.owned_by });
99
+ }
100
+ return results;
101
+ } catch {
102
+ return [];
103
+ }
104
+ }
105
+
106
+ const PROVIDER_NAMES: Record<string, string> = {
107
+ anthropic: "Anthropic",
108
+ openai: "OpenAI",
109
+ "google-vertex": "Gemini",
110
+ };
111
+
112
+ export function getProviderName(ownedBy: string): string {
113
+ return PROVIDER_NAMES[ownedBy] ?? ownedBy;
114
+ }
115
+
116
+ const PROVIDER_PREFIXES = ["anthropic/", "openai/", "google-vertex/"];
117
+
118
+ export function formatGatewayModelName(model: GatewayModel): string {
119
+ let cleanId = model.id;
120
+ for (const prefix of PROVIDER_PREFIXES) {
121
+ if (cleanId.startsWith(prefix)) {
122
+ cleanId = cleanId.slice(prefix.length);
123
+ break;
124
+ }
125
+ }
126
+
127
+ cleanId = cleanId.replace(/(\d)-(\d)/g, "$1.$2");
128
+
129
+ const words = cleanId.split(/[-_]/).map((word) => {
130
+ if (word.match(/^[0-9.]+$/)) return word;
131
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
132
+ });
133
+
134
+ return words.join(" ");
135
+ }
package/src/index.ts ADDED
@@ -0,0 +1,79 @@
1
+ export type {
2
+ BranchCreatedPayload,
3
+ CompactBoundaryPayload,
4
+ ConsoleNotificationPayload,
5
+ ErrorNotificationPayload,
6
+ ModeChangePayload,
7
+ PostHogNotificationPayload,
8
+ PostHogNotificationType,
9
+ RunStartedPayload,
10
+ SdkSessionPayload,
11
+ SessionResumePayload,
12
+ StatusPayload,
13
+ TaskCompletePayload,
14
+ TaskNotificationPayload,
15
+ TreeSnapshotPayload,
16
+ UserMessagePayload,
17
+ } from "./acp-extensions.js";
18
+ export { POSTHOG_NOTIFICATIONS } from "./acp-extensions.js";
19
+ export type {
20
+ AcpConnection,
21
+ AcpConnectionConfig,
22
+ AgentAdapter,
23
+ InProcessAcpConnection,
24
+ } from "./adapters/acp-connection.js";
25
+ export { createAcpConnection } from "./adapters/acp-connection.js";
26
+ export {
27
+ fetchMcpToolMetadata,
28
+ isMcpToolReadOnly,
29
+ } from "./adapters/claude/mcp/tool-metadata.js";
30
+ export type { CodexProcessOptions } from "./adapters/codex/spawn.js";
31
+ export { Agent } from "./agent.js";
32
+ export {
33
+ type ArrayModelInfo,
34
+ BLOCKED_MODELS,
35
+ DEFAULT_GATEWAY_MODEL,
36
+ type FetchGatewayModelsOptions,
37
+ fetchArrayModels,
38
+ fetchGatewayModels,
39
+ formatGatewayModelName,
40
+ type GatewayModel,
41
+ getProviderName,
42
+ isAnthropicModel,
43
+ } from "./gateway-models.js";
44
+ export type { OtelLogConfig, SessionContext } from "./otel-log-writer.js";
45
+ export { OtelLogWriter } from "./otel-log-writer.js";
46
+ export { PostHogAPIClient } from "./posthog-api.js";
47
+ export type {
48
+ ConversationTurn,
49
+ ResumeConfig,
50
+ ResumeState,
51
+ ToolCallInfo,
52
+ } from "./resume.js";
53
+ export { conversationToPromptHistory, resumeFromLog } from "./resume.js";
54
+ export type { SessionLogWriterOptions } from "./session-log-writer.js";
55
+ export { SessionLogWriter } from "./session-log-writer.js";
56
+ export type { TreeSnapshot, TreeTrackerConfig } from "./tree-tracker.js";
57
+ export {
58
+ isCommitOnRemote,
59
+ TreeTracker,
60
+ validateForCloudHandoff,
61
+ } from "./tree-tracker.js";
62
+ export type {
63
+ AgentConfig,
64
+ AgentMode,
65
+ DeviceInfo,
66
+ FileChange,
67
+ FileStatus,
68
+ LogLevel,
69
+ OnLogCallback,
70
+ OtelTransportConfig,
71
+ StoredEntry,
72
+ StoredNotification,
73
+ Task,
74
+ TaskRun,
75
+ TreeSnapshotEvent,
76
+ } from "./types.js";
77
+ export { getLlmGatewayUrl } from "./utils/gateway.js";
78
+ export type { LoggerConfig } from "./utils/logger.js";
79
+ export { Logger } from "./utils/logger.js";
@@ -0,0 +1,105 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { OtelLogWriter } from "./otel-log-writer.js";
3
+ import type { StoredNotification } from "./types.js";
4
+
5
+ // Mock the OTEL exporter
6
+ const mockExport = vi.fn((_logs, callback) => {
7
+ callback({ code: 0 }); // Success
8
+ });
9
+
10
+ vi.mock("@opentelemetry/exporter-logs-otlp-http", () => ({
11
+ OTLPLogExporter: vi.fn().mockImplementation(() => ({
12
+ export: mockExport,
13
+ shutdown: vi.fn().mockResolvedValue(undefined),
14
+ })),
15
+ }));
16
+
17
+ describe("OtelLogWriter", () => {
18
+ let writer: OtelLogWriter;
19
+
20
+ beforeEach(() => {
21
+ mockExport.mockClear();
22
+ // Session context (taskId, runId) is now passed in constructor as resource attributes
23
+ writer = new OtelLogWriter(
24
+ {
25
+ posthogHost: "https://us.i.posthog.com",
26
+ apiKey: "phc_test_key",
27
+ flushIntervalMs: 100,
28
+ },
29
+ {
30
+ taskId: "task-123",
31
+ runId: "run-456",
32
+ },
33
+ );
34
+ });
35
+
36
+ afterEach(async () => {
37
+ await writer.shutdown();
38
+ });
39
+
40
+ it("should emit a log entry with event_type as regular attribute", async () => {
41
+ const notification: StoredNotification = {
42
+ type: "notification",
43
+ timestamp: new Date().toISOString(),
44
+ notification: {
45
+ jsonrpc: "2.0",
46
+ method: "_posthog/test_event",
47
+ params: { foo: "bar" },
48
+ },
49
+ };
50
+
51
+ // taskId and runId are now resource attributes set in constructor,
52
+ // only notification is passed per-emit
53
+ writer.emit({ notification });
54
+
55
+ // Force flush to trigger export
56
+ await writer.flush();
57
+
58
+ // Verify export was called
59
+ expect(mockExport).toHaveBeenCalled();
60
+
61
+ // Get the logs that were exported
62
+ const exportedLogs = mockExport.mock.calls[0][0];
63
+ expect(exportedLogs.length).toBe(1);
64
+
65
+ const log = exportedLogs[0];
66
+ // task_id and run_id are now resource attributes, not regular attributes
67
+ expect(log.attributes.task_id).toBeUndefined();
68
+ expect(log.attributes.run_id).toBeUndefined();
69
+ // event_type is still a regular attribute (varies per log entry)
70
+ expect(log.attributes.event_type).toBe("_posthog/test_event");
71
+ expect(log.body).toBe(JSON.stringify(notification));
72
+
73
+ // Verify resource attributes contain task_id and run_id
74
+ expect(log.resource.attributes.task_id).toBe("task-123");
75
+ expect(log.resource.attributes.run_id).toBe("run-456");
76
+ expect(log.resource.attributes["service.name"]).toBe("twig-agent");
77
+ });
78
+
79
+ it("should batch multiple log entries", async () => {
80
+ const makeNotification = (method: string): StoredNotification => ({
81
+ type: "notification",
82
+ timestamp: new Date().toISOString(),
83
+ notification: {
84
+ jsonrpc: "2.0",
85
+ method,
86
+ },
87
+ });
88
+
89
+ writer.emit({ notification: makeNotification("event_1") });
90
+ writer.emit({ notification: makeNotification("event_2") });
91
+ writer.emit({ notification: makeNotification("event_3") });
92
+
93
+ await writer.flush();
94
+
95
+ expect(mockExport).toHaveBeenCalled();
96
+ const exportedLogs = mockExport.mock.calls[0][0];
97
+ expect(exportedLogs.length).toBe(3);
98
+
99
+ // All logs should share the same resource attributes
100
+ for (const log of exportedLogs) {
101
+ expect(log.resource.attributes.task_id).toBe("task-123");
102
+ expect(log.resource.attributes.run_id).toBe("run-456");
103
+ }
104
+ });
105
+ });
@@ -0,0 +1,94 @@
1
+ import { SeverityNumber } from "@opentelemetry/api-logs";
2
+ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
3
+ import { resourceFromAttributes } from "@opentelemetry/resources";
4
+ import {
5
+ BatchLogRecordProcessor,
6
+ LoggerProvider,
7
+ } from "@opentelemetry/sdk-logs";
8
+ import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
9
+ import type { StoredNotification } from "./types.js";
10
+ import type { Logger } from "./utils/logger.js";
11
+
12
+ export interface OtelLogConfig {
13
+ /** PostHog ingest host, e.g., "https://us.i.posthog.com" */
14
+ posthogHost: string;
15
+ /** Project API key, e.g., "phc_xxx" */
16
+ apiKey: string;
17
+ /** Batch flush interval in ms (default: 500) */
18
+ flushIntervalMs?: number;
19
+ /** Override the logs endpoint path (default: /i/v1/agent-logs) */
20
+ logsPath?: string;
21
+ }
22
+
23
+ /**
24
+ * Session context for resource attributes.
25
+ * These are set once per OTEL logger instance and indexed via resource_fingerprint
26
+ */
27
+ export interface SessionContext {
28
+ /** Parent task grouping - all runs for a task share this */
29
+ taskId: string;
30
+ /** Primary conversation identifier - all events in a run share this */
31
+ runId: string;
32
+ /** Deployment environment - "local" for desktop, "cloud" for cloud sandbox */
33
+ deviceType?: "local" | "cloud";
34
+ }
35
+
36
+ export class OtelLogWriter {
37
+ private loggerProvider: LoggerProvider;
38
+ private logger: ReturnType<LoggerProvider["getLogger"]>;
39
+
40
+ constructor(
41
+ config: OtelLogConfig,
42
+ sessionContext: SessionContext,
43
+ _debugLogger?: Logger,
44
+ ) {
45
+ const logsPath = config.logsPath ?? "/i/v1/agent-logs";
46
+ const exporter = new OTLPLogExporter({
47
+ url: `${config.posthogHost}${logsPath}`,
48
+ headers: { Authorization: `Bearer ${config.apiKey}` },
49
+ });
50
+
51
+ const processor = new BatchLogRecordProcessor(exporter, {
52
+ scheduledDelayMillis: config.flushIntervalMs ?? 500,
53
+ });
54
+
55
+ // Resource attributes are set ONCE per session and indexed via resource_fingerprint
56
+ // So we have fast queries by run_id/task_id in PostHog Logs UI
57
+ this.loggerProvider = new LoggerProvider({
58
+ resource: resourceFromAttributes({
59
+ [ATTR_SERVICE_NAME]: "twig-agent",
60
+ run_id: sessionContext.runId,
61
+ task_id: sessionContext.taskId,
62
+ device_type: sessionContext.deviceType ?? "local",
63
+ }),
64
+ processors: [processor],
65
+ });
66
+
67
+ this.logger = this.loggerProvider.getLogger("agent-session");
68
+ }
69
+
70
+ /**
71
+ * Emit an agent event to PostHog Logs via OTEL.
72
+ */
73
+ emit(entry: { notification: StoredNotification }): void {
74
+ const { notification } = entry;
75
+ const eventType = notification.notification.method;
76
+
77
+ this.logger.emit({
78
+ severityNumber: SeverityNumber.INFO,
79
+ severityText: "INFO",
80
+ body: JSON.stringify(notification),
81
+ attributes: {
82
+ event_type: eventType,
83
+ },
84
+ });
85
+ }
86
+
87
+ async flush(): Promise<void> {
88
+ await this.loggerProvider.forceFlush();
89
+ }
90
+
91
+ async shutdown(): Promise<void> {
92
+ await this.loggerProvider.shutdown();
93
+ }
94
+ }