@posthog/agent 2.1.22 → 2.1.35
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/dist/adapters/claude/permissions/permission-options.js +1 -1
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
- package/dist/adapters/claude/tools.js +1 -1
- package/dist/adapters/claude/tools.js.map +1 -1
- package/dist/{agent-LrKyX9KN.d.ts → agent-DcBmoTR4.d.ts} +7 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +84 -8
- package/dist/agent.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +84 -8
- package/dist/index.js.map +1 -1
- package/dist/server/agent-server.js +103 -9
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +103 -9
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude/tools.ts +1 -1
- package/src/server/agent-server.ts +21 -1
- package/src/session-log-writer.test.ts +41 -0
- package/src/session-log-writer.ts +91 -5
|
@@ -19,7 +19,7 @@ var BASH_TOOLS = /* @__PURE__ */ new Set([
|
|
|
19
19
|
]);
|
|
20
20
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set(["Glob", "Grep", "LS"]);
|
|
21
21
|
var WEB_TOOLS = /* @__PURE__ */ new Set(["WebSearch", "WebFetch"]);
|
|
22
|
-
var AGENT_TOOLS = /* @__PURE__ */ new Set(["Task", "TodoWrite"]);
|
|
22
|
+
var AGENT_TOOLS = /* @__PURE__ */ new Set(["Task", "TodoWrite", "Skill"]);
|
|
23
23
|
var BASE_ALLOWED_TOOLS = [
|
|
24
24
|
...READ_TOOLS,
|
|
25
25
|
...SEARCH_TOOLS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/utils/common.ts","../../../../src/adapters/claude/mcp/tool-metadata.ts","../../../../src/adapters/claude/tools.ts","../../../../src/adapters/claude/permissions/permission-options.ts"],"sourcesContent":["import type { Logger } from \"./logger.js\";\n\nexport const IS_ROOT =\n typeof process !== \"undefined\" &&\n (process.geteuid?.() ?? process.getuid?.()) === 0;\n\nexport function unreachable(value: never, logger: Logger): void {\n let valueAsString: string;\n try {\n valueAsString = JSON.stringify(value);\n } catch {\n valueAsString = value;\n }\n logger.error(`Unexpected case: ${valueAsString}`);\n}\n","import type { McpServerConfig } from \"@anthropic-ai/claude-agent-sdk\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport { Logger } from \"../../../utils/logger.js\";\n\nexport interface McpToolMetadata {\n readOnly: boolean;\n}\n\nconst mcpToolMetadataCache: Map<string, McpToolMetadata> = new Map();\n\nfunction buildToolKey(serverName: string, toolName: string): string {\n return `mcp__${serverName}__${toolName}`;\n}\n\nfunction isHttpMcpServer(\n config: McpServerConfig,\n): config is McpServerConfig & { type: \"http\"; url: string } {\n return config.type === \"http\" && typeof (config as any).url === \"string\";\n}\n\nasync function fetchToolsFromHttpServer(\n _serverName: string,\n config: McpServerConfig & { type: \"http\"; url: string },\n): Promise<Tool[]> {\n const transport = new StreamableHTTPClientTransport(new URL(config.url), {\n requestInit: {\n headers: (config as any).headers || {},\n },\n });\n\n const client = new Client({\n name: \"twig-metadata-fetcher\",\n version: \"1.0.0\",\n });\n\n try {\n await client.connect(transport);\n const result = await client.listTools();\n return result.tools;\n } finally {\n await client.close().catch(() => {});\n }\n}\n\nfunction extractToolMetadata(tool: Tool): McpToolMetadata {\n return {\n readOnly: tool.annotations?.readOnlyHint === true,\n };\n}\n\nexport async function fetchMcpToolMetadata(\n mcpServers: Record<string, McpServerConfig>,\n logger: Logger = new Logger({ debug: false, prefix: \"[McpToolMetadata]\" }),\n): Promise<void> {\n const fetchPromises: Promise<void>[] = [];\n\n for (const [serverName, config] of Object.entries(mcpServers)) {\n if (!isHttpMcpServer(config)) {\n continue;\n }\n\n const fetchPromise = fetchToolsFromHttpServer(serverName, config)\n .then((tools) => {\n const toolCount = tools.length;\n const readOnlyCount = tools.filter(\n (t) => t.annotations?.readOnlyHint === true,\n ).length;\n\n for (const tool of tools) {\n const toolKey = buildToolKey(serverName, tool.name);\n mcpToolMetadataCache.set(toolKey, extractToolMetadata(tool));\n }\n\n logger.info(\"Fetched MCP tool metadata\", {\n serverName,\n toolCount,\n readOnlyCount,\n });\n })\n .catch((error) => {\n logger.error(\"Failed to fetch MCP tool metadata\", {\n serverName,\n error: error instanceof Error ? error.message : String(error),\n });\n });\n\n fetchPromises.push(fetchPromise);\n }\n\n await Promise.all(fetchPromises);\n}\n\nexport function isMcpToolReadOnly(toolName: string): boolean {\n const metadata = mcpToolMetadataCache.get(toolName);\n return metadata?.readOnly === true;\n}\n\nexport function clearMcpToolMetadataCache(): void {\n mcpToolMetadataCache.clear();\n}\n","export {\n getAvailableModes,\n type ModeInfo,\n TWIG_EXECUTION_MODES,\n type TwigExecutionMode,\n} from \"../../execution-mode.js\";\n\nimport type { TwigExecutionMode } from \"../../execution-mode.js\";\nimport { isMcpToolReadOnly } from \"./mcp/tool-metadata.js\";\n\nexport const READ_TOOLS: Set<string> = new Set([\"Read\", \"NotebookRead\"]);\n\nexport const WRITE_TOOLS: Set<string> = new Set([\n \"Edit\",\n \"Write\",\n \"NotebookEdit\",\n]);\n\nexport const BASH_TOOLS: Set<string> = new Set([\n \"Bash\",\n \"BashOutput\",\n \"KillShell\",\n]);\n\nexport const SEARCH_TOOLS: Set<string> = new Set([\"Glob\", \"Grep\", \"LS\"]);\n\nexport const WEB_TOOLS: Set<string> = new Set([\"WebSearch\", \"WebFetch\"]);\n\nexport const AGENT_TOOLS: Set<string> = new Set([\"Task\", \"TodoWrite\"]);\n\nconst BASE_ALLOWED_TOOLS = [\n ...READ_TOOLS,\n ...SEARCH_TOOLS,\n ...WEB_TOOLS,\n ...AGENT_TOOLS,\n];\n\nconst AUTO_ALLOWED_TOOLS: Record<string, Set<string>> = {\n default: new Set(BASE_ALLOWED_TOOLS),\n acceptEdits: new Set([...BASE_ALLOWED_TOOLS, ...WRITE_TOOLS]),\n plan: new Set(BASE_ALLOWED_TOOLS),\n};\n\nexport function isToolAllowedForMode(\n toolName: string,\n mode: TwigExecutionMode,\n): boolean {\n if (mode === \"bypassPermissions\") {\n return true;\n }\n if (AUTO_ALLOWED_TOOLS[mode]?.has(toolName) === true) {\n return true;\n }\n if (isMcpToolReadOnly(toolName)) {\n return true;\n }\n return false;\n}\n","import { BASH_TOOLS, READ_TOOLS, SEARCH_TOOLS, WRITE_TOOLS } from \"../tools.js\";\n\nexport interface PermissionOption {\n kind: \"allow_once\" | \"allow_always\" | \"reject_once\" | \"reject_always\";\n name: string;\n optionId: string;\n _meta?: { description?: string; customInput?: boolean };\n}\n\nfunction permissionOptions(allowAlwaysLabel: string): PermissionOption[] {\n return [\n { kind: \"allow_once\", name: \"Yes\", optionId: \"allow\" },\n { kind: \"allow_always\", name: allowAlwaysLabel, optionId: \"allow_always\" },\n {\n kind: \"reject_once\",\n name: \"No, and tell the agent what to do differently\",\n optionId: \"reject\",\n _meta: { customInput: true },\n },\n ];\n}\n\nexport function buildPermissionOptions(\n toolName: string,\n toolInput: Record<string, unknown>,\n cwd?: string,\n): PermissionOption[] {\n if (BASH_TOOLS.has(toolName)) {\n const command = toolInput?.command as string | undefined;\n const cmdName = command?.split(/\\s+/)[0] ?? \"this command\";\n const cwdLabel = cwd ? ` in ${cwd}` : \"\";\n return permissionOptions(\n `Yes, and don't ask again for \\`${cmdName}\\` commands${cwdLabel}`,\n );\n }\n\n if (toolName === \"BashOutput\") {\n return permissionOptions(\"Yes, allow all background process reads\");\n }\n\n if (toolName === \"KillShell\") {\n return permissionOptions(\"Yes, allow killing processes\");\n }\n\n if (WRITE_TOOLS.has(toolName)) {\n return permissionOptions(\"Yes, allow all edits during this session\");\n }\n\n if (READ_TOOLS.has(toolName)) {\n return permissionOptions(\"Yes, allow all reads during this session\");\n }\n\n if (SEARCH_TOOLS.has(toolName)) {\n return permissionOptions(\"Yes, allow all searches during this session\");\n }\n\n if (toolName === \"WebFetch\") {\n const url = toolInput?.url as string | undefined;\n let domain = \"\";\n try {\n domain = url ? new URL(url).hostname : \"\";\n } catch {}\n return permissionOptions(\n domain\n ? `Yes, allow all fetches from ${domain}`\n : \"Yes, allow all fetches\",\n );\n }\n\n if (toolName === \"WebSearch\") {\n return permissionOptions(\"Yes, allow all web searches\");\n }\n\n if (toolName === \"Task\") {\n return permissionOptions(\"Yes, allow all sub-tasks\");\n }\n\n if (toolName === \"TodoWrite\") {\n return permissionOptions(\"Yes, allow all todo updates\");\n }\n\n return permissionOptions(\"Yes, always allow\");\n}\n\nexport function buildExitPlanModePermissionOptions(): PermissionOption[] {\n return [\n {\n kind: \"allow_always\",\n name: \"Yes, and auto-accept edits\",\n optionId: \"acceptEdits\",\n },\n {\n kind: \"allow_once\",\n name: \"Yes, and manually approve edits\",\n optionId: \"default\",\n },\n {\n kind: \"reject_once\",\n name: \"No, keep planning\",\n optionId: \"plan\",\n },\n ];\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/utils/common.ts","../../../../src/adapters/claude/mcp/tool-metadata.ts","../../../../src/adapters/claude/tools.ts","../../../../src/adapters/claude/permissions/permission-options.ts"],"sourcesContent":["import type { Logger } from \"./logger.js\";\n\nexport const IS_ROOT =\n typeof process !== \"undefined\" &&\n (process.geteuid?.() ?? process.getuid?.()) === 0;\n\nexport function unreachable(value: never, logger: Logger): void {\n let valueAsString: string;\n try {\n valueAsString = JSON.stringify(value);\n } catch {\n valueAsString = value;\n }\n logger.error(`Unexpected case: ${valueAsString}`);\n}\n","import type { McpServerConfig } from \"@anthropic-ai/claude-agent-sdk\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport { Logger } from \"../../../utils/logger.js\";\n\nexport interface McpToolMetadata {\n readOnly: boolean;\n}\n\nconst mcpToolMetadataCache: Map<string, McpToolMetadata> = new Map();\n\nfunction buildToolKey(serverName: string, toolName: string): string {\n return `mcp__${serverName}__${toolName}`;\n}\n\nfunction isHttpMcpServer(\n config: McpServerConfig,\n): config is McpServerConfig & { type: \"http\"; url: string } {\n return config.type === \"http\" && typeof (config as any).url === \"string\";\n}\n\nasync function fetchToolsFromHttpServer(\n _serverName: string,\n config: McpServerConfig & { type: \"http\"; url: string },\n): Promise<Tool[]> {\n const transport = new StreamableHTTPClientTransport(new URL(config.url), {\n requestInit: {\n headers: (config as any).headers || {},\n },\n });\n\n const client = new Client({\n name: \"twig-metadata-fetcher\",\n version: \"1.0.0\",\n });\n\n try {\n await client.connect(transport);\n const result = await client.listTools();\n return result.tools;\n } finally {\n await client.close().catch(() => {});\n }\n}\n\nfunction extractToolMetadata(tool: Tool): McpToolMetadata {\n return {\n readOnly: tool.annotations?.readOnlyHint === true,\n };\n}\n\nexport async function fetchMcpToolMetadata(\n mcpServers: Record<string, McpServerConfig>,\n logger: Logger = new Logger({ debug: false, prefix: \"[McpToolMetadata]\" }),\n): Promise<void> {\n const fetchPromises: Promise<void>[] = [];\n\n for (const [serverName, config] of Object.entries(mcpServers)) {\n if (!isHttpMcpServer(config)) {\n continue;\n }\n\n const fetchPromise = fetchToolsFromHttpServer(serverName, config)\n .then((tools) => {\n const toolCount = tools.length;\n const readOnlyCount = tools.filter(\n (t) => t.annotations?.readOnlyHint === true,\n ).length;\n\n for (const tool of tools) {\n const toolKey = buildToolKey(serverName, tool.name);\n mcpToolMetadataCache.set(toolKey, extractToolMetadata(tool));\n }\n\n logger.info(\"Fetched MCP tool metadata\", {\n serverName,\n toolCount,\n readOnlyCount,\n });\n })\n .catch((error) => {\n logger.error(\"Failed to fetch MCP tool metadata\", {\n serverName,\n error: error instanceof Error ? error.message : String(error),\n });\n });\n\n fetchPromises.push(fetchPromise);\n }\n\n await Promise.all(fetchPromises);\n}\n\nexport function isMcpToolReadOnly(toolName: string): boolean {\n const metadata = mcpToolMetadataCache.get(toolName);\n return metadata?.readOnly === true;\n}\n\nexport function clearMcpToolMetadataCache(): void {\n mcpToolMetadataCache.clear();\n}\n","export {\n getAvailableModes,\n type ModeInfo,\n TWIG_EXECUTION_MODES,\n type TwigExecutionMode,\n} from \"../../execution-mode.js\";\n\nimport type { TwigExecutionMode } from \"../../execution-mode.js\";\nimport { isMcpToolReadOnly } from \"./mcp/tool-metadata.js\";\n\nexport const READ_TOOLS: Set<string> = new Set([\"Read\", \"NotebookRead\"]);\n\nexport const WRITE_TOOLS: Set<string> = new Set([\n \"Edit\",\n \"Write\",\n \"NotebookEdit\",\n]);\n\nexport const BASH_TOOLS: Set<string> = new Set([\n \"Bash\",\n \"BashOutput\",\n \"KillShell\",\n]);\n\nexport const SEARCH_TOOLS: Set<string> = new Set([\"Glob\", \"Grep\", \"LS\"]);\n\nexport const WEB_TOOLS: Set<string> = new Set([\"WebSearch\", \"WebFetch\"]);\n\nexport const AGENT_TOOLS: Set<string> = new Set([\"Task\", \"TodoWrite\", \"Skill\"]);\n\nconst BASE_ALLOWED_TOOLS = [\n ...READ_TOOLS,\n ...SEARCH_TOOLS,\n ...WEB_TOOLS,\n ...AGENT_TOOLS,\n];\n\nconst AUTO_ALLOWED_TOOLS: Record<string, Set<string>> = {\n default: new Set(BASE_ALLOWED_TOOLS),\n acceptEdits: new Set([...BASE_ALLOWED_TOOLS, ...WRITE_TOOLS]),\n plan: new Set(BASE_ALLOWED_TOOLS),\n};\n\nexport function isToolAllowedForMode(\n toolName: string,\n mode: TwigExecutionMode,\n): boolean {\n if (mode === \"bypassPermissions\") {\n return true;\n }\n if (AUTO_ALLOWED_TOOLS[mode]?.has(toolName) === true) {\n return true;\n }\n if (isMcpToolReadOnly(toolName)) {\n return true;\n }\n return false;\n}\n","import { BASH_TOOLS, READ_TOOLS, SEARCH_TOOLS, WRITE_TOOLS } from \"../tools.js\";\n\nexport interface PermissionOption {\n kind: \"allow_once\" | \"allow_always\" | \"reject_once\" | \"reject_always\";\n name: string;\n optionId: string;\n _meta?: { description?: string; customInput?: boolean };\n}\n\nfunction permissionOptions(allowAlwaysLabel: string): PermissionOption[] {\n return [\n { kind: \"allow_once\", name: \"Yes\", optionId: \"allow\" },\n { kind: \"allow_always\", name: allowAlwaysLabel, optionId: \"allow_always\" },\n {\n kind: \"reject_once\",\n name: \"No, and tell the agent what to do differently\",\n optionId: \"reject\",\n _meta: { customInput: true },\n },\n ];\n}\n\nexport function buildPermissionOptions(\n toolName: string,\n toolInput: Record<string, unknown>,\n cwd?: string,\n): PermissionOption[] {\n if (BASH_TOOLS.has(toolName)) {\n const command = toolInput?.command as string | undefined;\n const cmdName = command?.split(/\\s+/)[0] ?? \"this command\";\n const cwdLabel = cwd ? ` in ${cwd}` : \"\";\n return permissionOptions(\n `Yes, and don't ask again for \\`${cmdName}\\` commands${cwdLabel}`,\n );\n }\n\n if (toolName === \"BashOutput\") {\n return permissionOptions(\"Yes, allow all background process reads\");\n }\n\n if (toolName === \"KillShell\") {\n return permissionOptions(\"Yes, allow killing processes\");\n }\n\n if (WRITE_TOOLS.has(toolName)) {\n return permissionOptions(\"Yes, allow all edits during this session\");\n }\n\n if (READ_TOOLS.has(toolName)) {\n return permissionOptions(\"Yes, allow all reads during this session\");\n }\n\n if (SEARCH_TOOLS.has(toolName)) {\n return permissionOptions(\"Yes, allow all searches during this session\");\n }\n\n if (toolName === \"WebFetch\") {\n const url = toolInput?.url as string | undefined;\n let domain = \"\";\n try {\n domain = url ? new URL(url).hostname : \"\";\n } catch {}\n return permissionOptions(\n domain\n ? `Yes, allow all fetches from ${domain}`\n : \"Yes, allow all fetches\",\n );\n }\n\n if (toolName === \"WebSearch\") {\n return permissionOptions(\"Yes, allow all web searches\");\n }\n\n if (toolName === \"Task\") {\n return permissionOptions(\"Yes, allow all sub-tasks\");\n }\n\n if (toolName === \"TodoWrite\") {\n return permissionOptions(\"Yes, allow all todo updates\");\n }\n\n return permissionOptions(\"Yes, always allow\");\n}\n\nexport function buildExitPlanModePermissionOptions(): PermissionOption[] {\n return [\n {\n kind: \"allow_always\",\n name: \"Yes, and auto-accept edits\",\n optionId: \"acceptEdits\",\n },\n {\n kind: \"allow_once\",\n name: \"Yes, and manually approve edits\",\n optionId: \"default\",\n },\n {\n kind: \"reject_once\",\n name: \"No, keep planning\",\n optionId: \"plan\",\n },\n ];\n}\n"],"mappings":"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","names":[]}
|
|
@@ -57,7 +57,7 @@ var BASH_TOOLS = /* @__PURE__ */ new Set([
|
|
|
57
57
|
]);
|
|
58
58
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set(["Glob", "Grep", "LS"]);
|
|
59
59
|
var WEB_TOOLS = /* @__PURE__ */ new Set(["WebSearch", "WebFetch"]);
|
|
60
|
-
var AGENT_TOOLS = /* @__PURE__ */ new Set(["Task", "TodoWrite"]);
|
|
60
|
+
var AGENT_TOOLS = /* @__PURE__ */ new Set(["Task", "TodoWrite", "Skill"]);
|
|
61
61
|
var BASE_ALLOWED_TOOLS = [
|
|
62
62
|
...READ_TOOLS,
|
|
63
63
|
...SEARCH_TOOLS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/common.ts","../../../src/execution-mode.ts","../../../src/adapters/claude/mcp/tool-metadata.ts","../../../src/adapters/claude/tools.ts"],"sourcesContent":["import type { Logger } from \"./logger.js\";\n\nexport const IS_ROOT =\n typeof process !== \"undefined\" &&\n (process.geteuid?.() ?? process.getuid?.()) === 0;\n\nexport function unreachable(value: never, logger: Logger): void {\n let valueAsString: string;\n try {\n valueAsString = JSON.stringify(value);\n } catch {\n valueAsString = value;\n }\n logger.error(`Unexpected case: ${valueAsString}`);\n}\n","import { IS_ROOT } from \"./utils/common.js\";\n\nexport interface ModeInfo {\n id: TwigExecutionMode;\n name: string;\n description: string;\n}\n\nconst MODES: ModeInfo[] = [\n {\n id: \"default\",\n name: \"Always Ask\",\n description: \"Prompts for permission on first use of each tool\",\n },\n {\n id: \"acceptEdits\",\n name: \"Accept Edits\",\n description: \"Automatically accepts file edit permissions for the session\",\n },\n {\n id: \"plan\",\n name: \"Plan Mode\",\n description: \"Claude can analyze but not modify files or execute commands\",\n },\n {\n id: \"bypassPermissions\",\n name: \"Bypass Permissions\",\n description: \"Skips all permission prompts\",\n },\n];\n\nexport const TWIG_EXECUTION_MODES = [\n \"default\",\n \"acceptEdits\",\n \"plan\",\n \"bypassPermissions\",\n] as const;\n\nexport type TwigExecutionMode = (typeof TWIG_EXECUTION_MODES)[number];\n\nexport function getAvailableModes(): ModeInfo[] {\n return IS_ROOT ? MODES.filter((m) => m.id !== \"bypassPermissions\") : MODES;\n}\n","import type { McpServerConfig } from \"@anthropic-ai/claude-agent-sdk\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport { Logger } from \"../../../utils/logger.js\";\n\nexport interface McpToolMetadata {\n readOnly: boolean;\n}\n\nconst mcpToolMetadataCache: Map<string, McpToolMetadata> = new Map();\n\nfunction buildToolKey(serverName: string, toolName: string): string {\n return `mcp__${serverName}__${toolName}`;\n}\n\nfunction isHttpMcpServer(\n config: McpServerConfig,\n): config is McpServerConfig & { type: \"http\"; url: string } {\n return config.type === \"http\" && typeof (config as any).url === \"string\";\n}\n\nasync function fetchToolsFromHttpServer(\n _serverName: string,\n config: McpServerConfig & { type: \"http\"; url: string },\n): Promise<Tool[]> {\n const transport = new StreamableHTTPClientTransport(new URL(config.url), {\n requestInit: {\n headers: (config as any).headers || {},\n },\n });\n\n const client = new Client({\n name: \"twig-metadata-fetcher\",\n version: \"1.0.0\",\n });\n\n try {\n await client.connect(transport);\n const result = await client.listTools();\n return result.tools;\n } finally {\n await client.close().catch(() => {});\n }\n}\n\nfunction extractToolMetadata(tool: Tool): McpToolMetadata {\n return {\n readOnly: tool.annotations?.readOnlyHint === true,\n };\n}\n\nexport async function fetchMcpToolMetadata(\n mcpServers: Record<string, McpServerConfig>,\n logger: Logger = new Logger({ debug: false, prefix: \"[McpToolMetadata]\" }),\n): Promise<void> {\n const fetchPromises: Promise<void>[] = [];\n\n for (const [serverName, config] of Object.entries(mcpServers)) {\n if (!isHttpMcpServer(config)) {\n continue;\n }\n\n const fetchPromise = fetchToolsFromHttpServer(serverName, config)\n .then((tools) => {\n const toolCount = tools.length;\n const readOnlyCount = tools.filter(\n (t) => t.annotations?.readOnlyHint === true,\n ).length;\n\n for (const tool of tools) {\n const toolKey = buildToolKey(serverName, tool.name);\n mcpToolMetadataCache.set(toolKey, extractToolMetadata(tool));\n }\n\n logger.info(\"Fetched MCP tool metadata\", {\n serverName,\n toolCount,\n readOnlyCount,\n });\n })\n .catch((error) => {\n logger.error(\"Failed to fetch MCP tool metadata\", {\n serverName,\n error: error instanceof Error ? error.message : String(error),\n });\n });\n\n fetchPromises.push(fetchPromise);\n }\n\n await Promise.all(fetchPromises);\n}\n\nexport function isMcpToolReadOnly(toolName: string): boolean {\n const metadata = mcpToolMetadataCache.get(toolName);\n return metadata?.readOnly === true;\n}\n\nexport function clearMcpToolMetadataCache(): void {\n mcpToolMetadataCache.clear();\n}\n","export {\n getAvailableModes,\n type ModeInfo,\n TWIG_EXECUTION_MODES,\n type TwigExecutionMode,\n} from \"../../execution-mode.js\";\n\nimport type { TwigExecutionMode } from \"../../execution-mode.js\";\nimport { isMcpToolReadOnly } from \"./mcp/tool-metadata.js\";\n\nexport const READ_TOOLS: Set<string> = new Set([\"Read\", \"NotebookRead\"]);\n\nexport const WRITE_TOOLS: Set<string> = new Set([\n \"Edit\",\n \"Write\",\n \"NotebookEdit\",\n]);\n\nexport const BASH_TOOLS: Set<string> = new Set([\n \"Bash\",\n \"BashOutput\",\n \"KillShell\",\n]);\n\nexport const SEARCH_TOOLS: Set<string> = new Set([\"Glob\", \"Grep\", \"LS\"]);\n\nexport const WEB_TOOLS: Set<string> = new Set([\"WebSearch\", \"WebFetch\"]);\n\nexport const AGENT_TOOLS: Set<string> = new Set([\"Task\", \"TodoWrite\"]);\n\nconst BASE_ALLOWED_TOOLS = [\n ...READ_TOOLS,\n ...SEARCH_TOOLS,\n ...WEB_TOOLS,\n ...AGENT_TOOLS,\n];\n\nconst AUTO_ALLOWED_TOOLS: Record<string, Set<string>> = {\n default: new Set(BASE_ALLOWED_TOOLS),\n acceptEdits: new Set([...BASE_ALLOWED_TOOLS, ...WRITE_TOOLS]),\n plan: new Set(BASE_ALLOWED_TOOLS),\n};\n\nexport function isToolAllowedForMode(\n toolName: string,\n mode: TwigExecutionMode,\n): boolean {\n if (mode === \"bypassPermissions\") {\n return true;\n }\n if (AUTO_ALLOWED_TOOLS[mode]?.has(toolName) === true) {\n return true;\n }\n if (isMcpToolReadOnly(toolName)) {\n return true;\n }\n return false;\n}\n"],"mappings":";AAEO,IAAM,UACX,OAAO,YAAY,gBAClB,QAAQ,UAAU,KAAK,QAAQ,SAAS,OAAO;;;ACIlD,IAAM,QAAoB;AAAA,EACxB;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AACF;AAEO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIO,SAAS,oBAAgC;AAC9C,SAAO,UAAU,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,mBAAmB,IAAI;AACvE;;;ACzCA,SAAS,cAAc;AACvB,SAAS,qCAAqC;AAQ9C,IAAM,uBAAqD,oBAAI,IAAI;AAoF5D,SAAS,kBAAkB,UAA2B;AAC3D,QAAM,WAAW,qBAAqB,IAAI,QAAQ;AAClD,SAAO,UAAU,aAAa;AAChC;;;ACvFO,IAAM,aAA0B,oBAAI,IAAI,CAAC,QAAQ,cAAc,CAAC;AAEhE,IAAM,cAA2B,oBAAI,IAAI;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,aAA0B,oBAAI,IAAI;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,eAA4B,oBAAI,IAAI,CAAC,QAAQ,QAAQ,IAAI,CAAC;AAEhE,IAAM,YAAyB,oBAAI,IAAI,CAAC,aAAa,UAAU,CAAC;AAEhE,IAAM,cAA2B,oBAAI,IAAI,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/common.ts","../../../src/execution-mode.ts","../../../src/adapters/claude/mcp/tool-metadata.ts","../../../src/adapters/claude/tools.ts"],"sourcesContent":["import type { Logger } from \"./logger.js\";\n\nexport const IS_ROOT =\n typeof process !== \"undefined\" &&\n (process.geteuid?.() ?? process.getuid?.()) === 0;\n\nexport function unreachable(value: never, logger: Logger): void {\n let valueAsString: string;\n try {\n valueAsString = JSON.stringify(value);\n } catch {\n valueAsString = value;\n }\n logger.error(`Unexpected case: ${valueAsString}`);\n}\n","import { IS_ROOT } from \"./utils/common.js\";\n\nexport interface ModeInfo {\n id: TwigExecutionMode;\n name: string;\n description: string;\n}\n\nconst MODES: ModeInfo[] = [\n {\n id: \"default\",\n name: \"Always Ask\",\n description: \"Prompts for permission on first use of each tool\",\n },\n {\n id: \"acceptEdits\",\n name: \"Accept Edits\",\n description: \"Automatically accepts file edit permissions for the session\",\n },\n {\n id: \"plan\",\n name: \"Plan Mode\",\n description: \"Claude can analyze but not modify files or execute commands\",\n },\n {\n id: \"bypassPermissions\",\n name: \"Bypass Permissions\",\n description: \"Skips all permission prompts\",\n },\n];\n\nexport const TWIG_EXECUTION_MODES = [\n \"default\",\n \"acceptEdits\",\n \"plan\",\n \"bypassPermissions\",\n] as const;\n\nexport type TwigExecutionMode = (typeof TWIG_EXECUTION_MODES)[number];\n\nexport function getAvailableModes(): ModeInfo[] {\n return IS_ROOT ? MODES.filter((m) => m.id !== \"bypassPermissions\") : MODES;\n}\n","import type { McpServerConfig } from \"@anthropic-ai/claude-agent-sdk\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport { Logger } from \"../../../utils/logger.js\";\n\nexport interface McpToolMetadata {\n readOnly: boolean;\n}\n\nconst mcpToolMetadataCache: Map<string, McpToolMetadata> = new Map();\n\nfunction buildToolKey(serverName: string, toolName: string): string {\n return `mcp__${serverName}__${toolName}`;\n}\n\nfunction isHttpMcpServer(\n config: McpServerConfig,\n): config is McpServerConfig & { type: \"http\"; url: string } {\n return config.type === \"http\" && typeof (config as any).url === \"string\";\n}\n\nasync function fetchToolsFromHttpServer(\n _serverName: string,\n config: McpServerConfig & { type: \"http\"; url: string },\n): Promise<Tool[]> {\n const transport = new StreamableHTTPClientTransport(new URL(config.url), {\n requestInit: {\n headers: (config as any).headers || {},\n },\n });\n\n const client = new Client({\n name: \"twig-metadata-fetcher\",\n version: \"1.0.0\",\n });\n\n try {\n await client.connect(transport);\n const result = await client.listTools();\n return result.tools;\n } finally {\n await client.close().catch(() => {});\n }\n}\n\nfunction extractToolMetadata(tool: Tool): McpToolMetadata {\n return {\n readOnly: tool.annotations?.readOnlyHint === true,\n };\n}\n\nexport async function fetchMcpToolMetadata(\n mcpServers: Record<string, McpServerConfig>,\n logger: Logger = new Logger({ debug: false, prefix: \"[McpToolMetadata]\" }),\n): Promise<void> {\n const fetchPromises: Promise<void>[] = [];\n\n for (const [serverName, config] of Object.entries(mcpServers)) {\n if (!isHttpMcpServer(config)) {\n continue;\n }\n\n const fetchPromise = fetchToolsFromHttpServer(serverName, config)\n .then((tools) => {\n const toolCount = tools.length;\n const readOnlyCount = tools.filter(\n (t) => t.annotations?.readOnlyHint === true,\n ).length;\n\n for (const tool of tools) {\n const toolKey = buildToolKey(serverName, tool.name);\n mcpToolMetadataCache.set(toolKey, extractToolMetadata(tool));\n }\n\n logger.info(\"Fetched MCP tool metadata\", {\n serverName,\n toolCount,\n readOnlyCount,\n });\n })\n .catch((error) => {\n logger.error(\"Failed to fetch MCP tool metadata\", {\n serverName,\n error: error instanceof Error ? error.message : String(error),\n });\n });\n\n fetchPromises.push(fetchPromise);\n }\n\n await Promise.all(fetchPromises);\n}\n\nexport function isMcpToolReadOnly(toolName: string): boolean {\n const metadata = mcpToolMetadataCache.get(toolName);\n return metadata?.readOnly === true;\n}\n\nexport function clearMcpToolMetadataCache(): void {\n mcpToolMetadataCache.clear();\n}\n","export {\n getAvailableModes,\n type ModeInfo,\n TWIG_EXECUTION_MODES,\n type TwigExecutionMode,\n} from \"../../execution-mode.js\";\n\nimport type { TwigExecutionMode } from \"../../execution-mode.js\";\nimport { isMcpToolReadOnly } from \"./mcp/tool-metadata.js\";\n\nexport const READ_TOOLS: Set<string> = new Set([\"Read\", \"NotebookRead\"]);\n\nexport const WRITE_TOOLS: Set<string> = new Set([\n \"Edit\",\n \"Write\",\n \"NotebookEdit\",\n]);\n\nexport const BASH_TOOLS: Set<string> = new Set([\n \"Bash\",\n \"BashOutput\",\n \"KillShell\",\n]);\n\nexport const SEARCH_TOOLS: Set<string> = new Set([\"Glob\", \"Grep\", \"LS\"]);\n\nexport const WEB_TOOLS: Set<string> = new Set([\"WebSearch\", \"WebFetch\"]);\n\nexport const AGENT_TOOLS: Set<string> = new Set([\"Task\", \"TodoWrite\", \"Skill\"]);\n\nconst BASE_ALLOWED_TOOLS = [\n ...READ_TOOLS,\n ...SEARCH_TOOLS,\n ...WEB_TOOLS,\n ...AGENT_TOOLS,\n];\n\nconst AUTO_ALLOWED_TOOLS: Record<string, Set<string>> = {\n default: new Set(BASE_ALLOWED_TOOLS),\n acceptEdits: new Set([...BASE_ALLOWED_TOOLS, ...WRITE_TOOLS]),\n plan: new Set(BASE_ALLOWED_TOOLS),\n};\n\nexport function isToolAllowedForMode(\n toolName: string,\n mode: TwigExecutionMode,\n): boolean {\n if (mode === \"bypassPermissions\") {\n return true;\n }\n if (AUTO_ALLOWED_TOOLS[mode]?.has(toolName) === true) {\n return true;\n }\n if (isMcpToolReadOnly(toolName)) {\n return true;\n }\n return false;\n}\n"],"mappings":";AAEO,IAAM,UACX,OAAO,YAAY,gBAClB,QAAQ,UAAU,KAAK,QAAQ,SAAS,OAAO;;;ACIlD,IAAM,QAAoB;AAAA,EACxB;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AACF;AAEO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIO,SAAS,oBAAgC;AAC9C,SAAO,UAAU,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,mBAAmB,IAAI;AACvE;;;ACzCA,SAAS,cAAc;AACvB,SAAS,qCAAqC;AAQ9C,IAAM,uBAAqD,oBAAI,IAAI;AAoF5D,SAAS,kBAAkB,UAA2B;AAC3D,QAAM,WAAW,qBAAqB,IAAI,QAAQ;AAClD,SAAO,UAAU,aAAa;AAChC;;;ACvFO,IAAM,aAA0B,oBAAI,IAAI,CAAC,QAAQ,cAAc,CAAC;AAEhE,IAAM,cAA2B,oBAAI,IAAI;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,aAA0B,oBAAI,IAAI;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,eAA4B,oBAAI,IAAI,CAAC,QAAQ,QAAQ,IAAI,CAAC;AAEhE,IAAM,YAAyB,oBAAI,IAAI,CAAC,aAAa,UAAU,CAAC;AAEhE,IAAM,cAA2B,oBAAI,IAAI,CAAC,QAAQ,aAAa,OAAO,CAAC;AAE9E,IAAM,qBAAqB;AAAA,EACzB,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAEA,IAAM,qBAAkD;AAAA,EACtD,SAAS,IAAI,IAAI,kBAAkB;AAAA,EACnC,aAAa,oBAAI,IAAI,CAAC,GAAG,oBAAoB,GAAG,WAAW,CAAC;AAAA,EAC5D,MAAM,IAAI,IAAI,kBAAkB;AAClC;AAEO,SAAS,qBACd,UACA,MACS;AACT,MAAI,SAAS,qBAAqB;AAChC,WAAO;AAAA,EACT;AACA,MAAI,mBAAmB,IAAI,GAAG,IAAI,QAAQ,MAAM,MAAM;AACpD,WAAO;AAAA,EACT;AACA,MAAI,kBAAkB,QAAQ,GAAG;AAC/B,WAAO;AAAA,EACT;AACA,SAAO;AACT;","names":[]}
|
|
@@ -46,10 +46,17 @@ interface SessionLogWriterOptions {
|
|
|
46
46
|
logger?: Logger;
|
|
47
47
|
}
|
|
48
48
|
declare class SessionLogWriter {
|
|
49
|
+
private static readonly FLUSH_DEBOUNCE_MS;
|
|
50
|
+
private static readonly FLUSH_MAX_INTERVAL_MS;
|
|
51
|
+
private static readonly MAX_FLUSH_RETRIES;
|
|
52
|
+
private static readonly MAX_RETRY_DELAY_MS;
|
|
49
53
|
private posthogAPI?;
|
|
50
54
|
private pendingEntries;
|
|
51
55
|
private flushTimeouts;
|
|
56
|
+
private lastFlushAttemptTime;
|
|
57
|
+
private retryCounts;
|
|
52
58
|
private sessions;
|
|
59
|
+
private messageCounts;
|
|
53
60
|
private logger;
|
|
54
61
|
constructor(options?: SessionLogWriterOptions);
|
|
55
62
|
flushAll(): Promise<void>;
|
package/dist/agent.d.ts
CHANGED
package/dist/agent.js
CHANGED
|
@@ -276,7 +276,7 @@ import { v7 as uuidv7 } from "uuid";
|
|
|
276
276
|
// package.json
|
|
277
277
|
var package_default = {
|
|
278
278
|
name: "@posthog/agent",
|
|
279
|
-
version: "2.1.
|
|
279
|
+
version: "2.1.35",
|
|
280
280
|
repository: "https://github.com/PostHog/twig",
|
|
281
281
|
description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
|
|
282
282
|
exports: {
|
|
@@ -1728,7 +1728,7 @@ var BASH_TOOLS = /* @__PURE__ */ new Set([
|
|
|
1728
1728
|
]);
|
|
1729
1729
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set(["Glob", "Grep", "LS"]);
|
|
1730
1730
|
var WEB_TOOLS = /* @__PURE__ */ new Set(["WebSearch", "WebFetch"]);
|
|
1731
|
-
var AGENT_TOOLS = /* @__PURE__ */ new Set(["Task", "TodoWrite"]);
|
|
1731
|
+
var AGENT_TOOLS = /* @__PURE__ */ new Set(["Task", "TodoWrite", "Skill"]);
|
|
1732
1732
|
var BASE_ALLOWED_TOOLS = [
|
|
1733
1733
|
...READ_TOOLS,
|
|
1734
1734
|
...SEARCH_TOOLS,
|
|
@@ -3386,19 +3386,36 @@ var PostHogAPIClient = class {
|
|
|
3386
3386
|
};
|
|
3387
3387
|
|
|
3388
3388
|
// src/session-log-writer.ts
|
|
3389
|
-
var SessionLogWriter = class {
|
|
3389
|
+
var SessionLogWriter = class _SessionLogWriter {
|
|
3390
|
+
static FLUSH_DEBOUNCE_MS = 500;
|
|
3391
|
+
static FLUSH_MAX_INTERVAL_MS = 5e3;
|
|
3392
|
+
static MAX_FLUSH_RETRIES = 10;
|
|
3393
|
+
static MAX_RETRY_DELAY_MS = 3e4;
|
|
3390
3394
|
posthogAPI;
|
|
3391
3395
|
pendingEntries = /* @__PURE__ */ new Map();
|
|
3392
3396
|
flushTimeouts = /* @__PURE__ */ new Map();
|
|
3397
|
+
lastFlushAttemptTime = /* @__PURE__ */ new Map();
|
|
3398
|
+
retryCounts = /* @__PURE__ */ new Map();
|
|
3393
3399
|
sessions = /* @__PURE__ */ new Map();
|
|
3400
|
+
messageCounts = /* @__PURE__ */ new Map();
|
|
3394
3401
|
logger;
|
|
3395
3402
|
constructor(options = {}) {
|
|
3396
3403
|
this.posthogAPI = options.posthogAPI;
|
|
3397
3404
|
this.logger = options.logger ?? new Logger({ debug: false, prefix: "[SessionLogWriter]" });
|
|
3398
3405
|
}
|
|
3399
3406
|
async flushAll() {
|
|
3407
|
+
const sessionIds = [...this.sessions.keys()];
|
|
3408
|
+
const pendingCounts = sessionIds.map((id) => ({
|
|
3409
|
+
id,
|
|
3410
|
+
pending: this.pendingEntries.get(id)?.length ?? 0,
|
|
3411
|
+
messages: this.messageCounts.get(id) ?? 0
|
|
3412
|
+
}));
|
|
3413
|
+
this.logger.info("flushAll called", {
|
|
3414
|
+
sessions: sessionIds.length,
|
|
3415
|
+
pending: pendingCounts
|
|
3416
|
+
});
|
|
3400
3417
|
const flushPromises = [];
|
|
3401
|
-
for (const sessionId of
|
|
3418
|
+
for (const sessionId of sessionIds) {
|
|
3402
3419
|
flushPromises.push(this.flush(sessionId));
|
|
3403
3420
|
}
|
|
3404
3421
|
await Promise.all(flushPromises);
|
|
@@ -3407,7 +3424,12 @@ var SessionLogWriter = class {
|
|
|
3407
3424
|
if (this.sessions.has(sessionId)) {
|
|
3408
3425
|
return;
|
|
3409
3426
|
}
|
|
3427
|
+
this.logger.info("Session registered", {
|
|
3428
|
+
sessionId,
|
|
3429
|
+
taskId: context.taskId
|
|
3430
|
+
});
|
|
3410
3431
|
this.sessions.set(sessionId, { context });
|
|
3432
|
+
this.lastFlushAttemptTime.set(sessionId, Date.now());
|
|
3411
3433
|
}
|
|
3412
3434
|
isRegistered(sessionId) {
|
|
3413
3435
|
return this.sessions.has(sessionId);
|
|
@@ -3415,8 +3437,16 @@ var SessionLogWriter = class {
|
|
|
3415
3437
|
appendRawLine(sessionId, line) {
|
|
3416
3438
|
const session = this.sessions.get(sessionId);
|
|
3417
3439
|
if (!session) {
|
|
3440
|
+
this.logger.warn("appendRawLine called for unregistered session", {
|
|
3441
|
+
sessionId
|
|
3442
|
+
});
|
|
3418
3443
|
return;
|
|
3419
3444
|
}
|
|
3445
|
+
const count = (this.messageCounts.get(sessionId) ?? 0) + 1;
|
|
3446
|
+
this.messageCounts.set(sessionId, count);
|
|
3447
|
+
if (count % 10 === 1) {
|
|
3448
|
+
this.logger.info("Messages received", { count, sessionId });
|
|
3449
|
+
}
|
|
3420
3450
|
try {
|
|
3421
3451
|
const message = JSON.parse(line);
|
|
3422
3452
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3452,24 +3482,56 @@ var SessionLogWriter = class {
|
|
|
3452
3482
|
}
|
|
3453
3483
|
async flush(sessionId) {
|
|
3454
3484
|
const session = this.sessions.get(sessionId);
|
|
3455
|
-
if (!session)
|
|
3485
|
+
if (!session) {
|
|
3486
|
+
this.logger.warn("flush: no session found", { sessionId });
|
|
3487
|
+
return;
|
|
3488
|
+
}
|
|
3456
3489
|
this.emitCoalescedMessage(sessionId, session);
|
|
3457
3490
|
const pending = this.pendingEntries.get(sessionId);
|
|
3458
|
-
if (!this.posthogAPI || !pending?.length)
|
|
3491
|
+
if (!this.posthogAPI || !pending?.length) {
|
|
3492
|
+
this.logger.info("flush: nothing to persist", {
|
|
3493
|
+
sessionId,
|
|
3494
|
+
hasPosthogAPI: !!this.posthogAPI,
|
|
3495
|
+
pendingCount: pending?.length ?? 0
|
|
3496
|
+
});
|
|
3497
|
+
return;
|
|
3498
|
+
}
|
|
3459
3499
|
this.pendingEntries.delete(sessionId);
|
|
3460
3500
|
const timeout = this.flushTimeouts.get(sessionId);
|
|
3461
3501
|
if (timeout) {
|
|
3462
3502
|
clearTimeout(timeout);
|
|
3463
3503
|
this.flushTimeouts.delete(sessionId);
|
|
3464
3504
|
}
|
|
3505
|
+
this.lastFlushAttemptTime.set(sessionId, Date.now());
|
|
3465
3506
|
try {
|
|
3466
3507
|
await this.posthogAPI.appendTaskRunLog(
|
|
3467
3508
|
session.context.taskId,
|
|
3468
3509
|
session.context.runId,
|
|
3469
3510
|
pending
|
|
3470
3511
|
);
|
|
3512
|
+
this.retryCounts.set(sessionId, 0);
|
|
3513
|
+
this.logger.info("Flushed session logs", {
|
|
3514
|
+
sessionId,
|
|
3515
|
+
entryCount: pending.length
|
|
3516
|
+
});
|
|
3471
3517
|
} catch (error) {
|
|
3472
|
-
this.
|
|
3518
|
+
const retryCount = (this.retryCounts.get(sessionId) ?? 0) + 1;
|
|
3519
|
+
this.retryCounts.set(sessionId, retryCount);
|
|
3520
|
+
if (retryCount >= _SessionLogWriter.MAX_FLUSH_RETRIES) {
|
|
3521
|
+
this.logger.error(
|
|
3522
|
+
`Dropping ${pending.length} session log entries after ${retryCount} failed flush attempts`,
|
|
3523
|
+
{ sessionId, error }
|
|
3524
|
+
);
|
|
3525
|
+
this.retryCounts.set(sessionId, 0);
|
|
3526
|
+
} else {
|
|
3527
|
+
this.logger.error(
|
|
3528
|
+
`Failed to persist session logs (attempt ${retryCount}/${_SessionLogWriter.MAX_FLUSH_RETRIES}):`,
|
|
3529
|
+
error
|
|
3530
|
+
);
|
|
3531
|
+
const currentPending = this.pendingEntries.get(sessionId) ?? [];
|
|
3532
|
+
this.pendingEntries.set(sessionId, [...pending, ...currentPending]);
|
|
3533
|
+
this.scheduleFlush(sessionId);
|
|
3534
|
+
}
|
|
3473
3535
|
}
|
|
3474
3536
|
}
|
|
3475
3537
|
isAgentMessageChunk(message) {
|
|
@@ -3515,7 +3577,21 @@ var SessionLogWriter = class {
|
|
|
3515
3577
|
scheduleFlush(sessionId) {
|
|
3516
3578
|
const existing = this.flushTimeouts.get(sessionId);
|
|
3517
3579
|
if (existing) clearTimeout(existing);
|
|
3518
|
-
const
|
|
3580
|
+
const retryCount = this.retryCounts.get(sessionId) ?? 0;
|
|
3581
|
+
const lastAttempt = this.lastFlushAttemptTime.get(sessionId) ?? 0;
|
|
3582
|
+
const elapsed = Date.now() - lastAttempt;
|
|
3583
|
+
let delay;
|
|
3584
|
+
if (retryCount > 0) {
|
|
3585
|
+
delay = Math.min(
|
|
3586
|
+
_SessionLogWriter.FLUSH_DEBOUNCE_MS * 2 ** retryCount,
|
|
3587
|
+
_SessionLogWriter.MAX_RETRY_DELAY_MS
|
|
3588
|
+
);
|
|
3589
|
+
} else if (elapsed >= _SessionLogWriter.FLUSH_MAX_INTERVAL_MS) {
|
|
3590
|
+
delay = 0;
|
|
3591
|
+
} else {
|
|
3592
|
+
delay = _SessionLogWriter.FLUSH_DEBOUNCE_MS;
|
|
3593
|
+
}
|
|
3594
|
+
const timeout = setTimeout(() => this.flush(sessionId), delay);
|
|
3519
3595
|
this.flushTimeouts.set(sessionId, timeout);
|
|
3520
3596
|
}
|
|
3521
3597
|
};
|