@posthog/agent 2.1.107 → 2.1.112
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 +0 -4
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
- package/dist/adapters/claude/tools.js +0 -2
- package/dist/adapters/claude/tools.js.map +1 -1
- package/dist/agent.js +57 -65
- package/dist/agent.js.map +1 -1
- package/dist/claude-cli/cli.js +1764 -1746
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/posthog-api.js +2 -3
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.js +60 -69
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +60 -69
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +2 -3
- package/src/adapters/claude/claude-agent.ts +8 -12
- package/src/adapters/claude/mcp/tool-metadata.ts +54 -72
- package/src/adapters/claude/session/options.ts +2 -1
- package/src/agent.ts +1 -1
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
// src/utils/common.ts
|
|
2
2
|
var IS_ROOT = typeof process !== "undefined" && (process.geteuid?.() ?? process.getuid?.()) === 0;
|
|
3
3
|
|
|
4
|
-
// src/adapters/claude/mcp/tool-metadata.ts
|
|
5
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
7
|
-
|
|
8
4
|
// src/adapters/claude/tools.ts
|
|
9
5
|
var READ_TOOLS = /* @__PURE__ */ new Set(["Read", "NotebookRead"]);
|
|
10
6
|
var WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -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\n/**\n * Races an operation against a timeout.\n * Returns success with the value if the operation completes in time,\n * or timeout if the operation takes longer than the specified duration.\n */\nexport async function withTimeout<T>(\n operation: Promise<T>,\n timeoutMs: number,\n): Promise<{ result: \"success\"; value: T } | { result: \"timeout\" }> {\n const timeoutPromise = new Promise<{ result: \"timeout\" }>((resolve) =>\n setTimeout(() => resolve({ result: \"timeout\" }), timeoutMs),\n );\n const operationPromise = operation.then((value) => ({\n result: \"success\" as const,\n value,\n }));\n return Promise.race([operationPromise, timeoutPromise]);\n}\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 {\n McpHttpServerConfig,\n McpServerConfig,\n} 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 McpHttpServerConfig {\n return config.type === \"http\" && typeof config.url === \"string\";\n}\n\nasync function fetchToolsFromHttpServer(\n _serverName: string,\n config: McpHttpServerConfig,\n): Promise<Tool[]> {\n const transport = new StreamableHTTPClientTransport(new URL(config.url), {\n requestInit: {\n headers: config.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 type { PermissionUpdate } from \"@anthropic-ai/claude-agent-sdk\";\nimport { 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 suggestions?: PermissionUpdate[],\n): PermissionOption[] {\n if (BASH_TOOLS.has(toolName)) {\n const rawRuleContent = suggestions\n ?.flatMap((s) => (\"rules\" in s ? s.rules : []))\n .find((r) => r.toolName === \"Bash\" && r.ruleContent)?.ruleContent;\n const ruleContent = rawRuleContent?.replace(/:?\\*$/, \"\");\n\n const command = toolInput?.command as string | undefined;\n const cmdName = command?.split(/\\s+/)[0] ?? \"this command\";\n const cwdLabel = cwd ? ` in ${cwd}` : \"\";\n const label = ruleContent ?? `\\`${cmdName}\\` commands`;\n\n return permissionOptions(\n `Yes, and don't ask again for ${label}${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/tools.ts","../../../../src/adapters/claude/permissions/permission-options.ts"],"sourcesContent":["import type { Logger } from \"./logger.js\";\n\n/**\n * Races an operation against a timeout.\n * Returns success with the value if the operation completes in time,\n * or timeout if the operation takes longer than the specified duration.\n */\nexport async function withTimeout<T>(\n operation: Promise<T>,\n timeoutMs: number,\n): Promise<{ result: \"success\"; value: T } | { result: \"timeout\" }> {\n const timeoutPromise = new Promise<{ result: \"timeout\" }>((resolve) =>\n setTimeout(() => resolve({ result: \"timeout\" }), timeoutMs),\n );\n const operationPromise = operation.then((value) => ({\n result: \"success\" as const,\n value,\n }));\n return Promise.race([operationPromise, timeoutPromise]);\n}\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","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 type { PermissionUpdate } from \"@anthropic-ai/claude-agent-sdk\";\nimport { 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 suggestions?: PermissionUpdate[],\n): PermissionOption[] {\n if (BASH_TOOLS.has(toolName)) {\n const rawRuleContent = suggestions\n ?.flatMap((s) => (\"rules\" in s ? s.rules : []))\n .find((r) => r.toolName === \"Bash\" && r.ruleContent)?.ruleContent;\n const ruleContent = rawRuleContent?.replace(/:?\\*$/, \"\");\n\n const command = toolInput?.command as string | undefined;\n const cmdName = command?.split(/\\s+/)[0] ?? \"this command\";\n const cwdLabel = cwd ? ` in ${cwd}` : \"\";\n const label = ruleContent ?? `\\`${cmdName}\\` commands`;\n\n return permissionOptions(\n `Yes, and don't ask again for ${label}${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":[]}
|
|
@@ -35,8 +35,6 @@ function getAvailableModes() {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// src/adapters/claude/mcp/tool-metadata.ts
|
|
38
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
39
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
40
38
|
var mcpToolMetadataCache = /* @__PURE__ */ new Map();
|
|
41
39
|
function isMcpToolReadOnly(toolName) {
|
|
42
40
|
const metadata = mcpToolMetadataCache.get(toolName);
|
|
@@ -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\n/**\n * Races an operation against a timeout.\n * Returns success with the value if the operation completes in time,\n * or timeout if the operation takes longer than the specified duration.\n */\nexport async function withTimeout<T>(\n operation: Promise<T>,\n timeoutMs: number,\n): Promise<{ result: \"success\"; value: T } | { result: \"timeout\" }> {\n const timeoutPromise = new Promise<{ result: \"timeout\" }>((resolve) =>\n setTimeout(() => resolve({ result: \"timeout\" }), timeoutMs),\n );\n const operationPromise = operation.then((value) => ({\n result: \"success\" as const,\n value,\n }));\n return Promise.race([operationPromise, timeoutPromise]);\n}\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 {
|
|
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\n/**\n * Races an operation against a timeout.\n * Returns success with the value if the operation completes in time,\n * or timeout if the operation takes longer than the specified duration.\n */\nexport async function withTimeout<T>(\n operation: Promise<T>,\n timeoutMs: number,\n): Promise<{ result: \"success\"; value: T } | { result: \"timeout\" }> {\n const timeoutPromise = new Promise<{ result: \"timeout\" }>((resolve) =>\n setTimeout(() => resolve({ result: \"timeout\" }), timeoutMs),\n );\n const operationPromise = operation.then((value) => ({\n result: \"success\" as const,\n value,\n }));\n return Promise.race([operationPromise, timeoutPromise]);\n}\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 { McpServerStatus, Query } from \"@anthropic-ai/claude-agent-sdk\";\nimport { Logger } from \"../../../utils/logger.js\";\n\nexport interface McpToolMetadata {\n readOnly: boolean;\n}\n\nconst mcpToolMetadataCache: Map<string, McpToolMetadata> = new Map();\n\nconst PENDING_RETRY_INTERVAL_MS = 1_000;\nconst PENDING_MAX_RETRIES = 10;\n\nfunction buildToolKey(serverName: string, toolName: string): string {\n return `mcp__${serverName}__${toolName}`;\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport async function fetchMcpToolMetadata(\n q: Query,\n logger: Logger = new Logger({ debug: false, prefix: \"[McpToolMetadata]\" }),\n): Promise<void> {\n let retries = 0;\n\n while (retries <= PENDING_MAX_RETRIES) {\n let statuses: McpServerStatus[];\n try {\n statuses = await q.mcpServerStatus();\n } catch (error) {\n logger.error(\"Failed to fetch MCP server status\", {\n error: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n\n const pendingServers = statuses.filter((s) => s.status === \"pending\");\n\n for (const server of statuses) {\n if (server.status !== \"connected\" || !server.tools) {\n continue;\n }\n\n let readOnlyCount = 0;\n for (const tool of server.tools) {\n const toolKey = buildToolKey(server.name, tool.name);\n const readOnly = tool.annotations?.readOnly === true;\n mcpToolMetadataCache.set(toolKey, { readOnly });\n if (readOnly) readOnlyCount++;\n }\n\n logger.info(\"Fetched MCP tool metadata\", {\n serverName: server.name,\n toolCount: server.tools.length,\n readOnlyCount,\n });\n }\n\n if (pendingServers.length === 0) {\n return;\n }\n\n retries++;\n if (retries > PENDING_MAX_RETRIES) {\n logger.warn(\"Gave up waiting for pending MCP servers\", {\n pendingServers: pendingServers.map((s) => s.name),\n });\n return;\n }\n\n logger.info(\"Waiting for pending MCP servers\", {\n pendingServers: pendingServers.map((s) => s.name),\n retry: retries,\n });\n await delay(PENDING_RETRY_INTERVAL_MS);\n }\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":";AAqBO,IAAM,UACX,OAAO,YAAY,gBAClB,QAAQ,UAAU,KAAK,QAAQ,SAAS,OAAO;;;ACflD,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;;;ACnCA,IAAM,uBAAqD,oBAAI,IAAI;AAwE5D,SAAS,kBAAkB,UAA2B;AAC3D,QAAM,WAAW,qBAAqB,IAAI,QAAQ;AAClD,SAAO,UAAU,aAAa;AAChC;;;ACxEO,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":[]}
|
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.112",
|
|
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: {
|
|
@@ -361,10 +361,9 @@ var package_default = {
|
|
|
361
361
|
},
|
|
362
362
|
dependencies: {
|
|
363
363
|
"@agentclientprotocol/sdk": "^0.14.0",
|
|
364
|
-
"@anthropic-ai/claude-agent-sdk": "0.2.
|
|
364
|
+
"@anthropic-ai/claude-agent-sdk": "0.2.59",
|
|
365
365
|
"@anthropic-ai/sdk": "^0.71.0",
|
|
366
366
|
"@hono/node-server": "^1.19.9",
|
|
367
|
-
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
368
367
|
"@opentelemetry/api-logs": "^0.208.0",
|
|
369
368
|
"@opentelemetry/exporter-logs-otlp-http": "^0.208.0",
|
|
370
369
|
"@opentelemetry/resources": "^2.0.0",
|
|
@@ -1614,68 +1613,61 @@ async function handleUserAssistantMessage(message, context) {
|
|
|
1614
1613
|
}
|
|
1615
1614
|
|
|
1616
1615
|
// src/adapters/claude/mcp/tool-metadata.ts
|
|
1617
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1618
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
1619
1616
|
var mcpToolMetadataCache = /* @__PURE__ */ new Map();
|
|
1617
|
+
var PENDING_RETRY_INTERVAL_MS = 1e3;
|
|
1618
|
+
var PENDING_MAX_RETRIES = 10;
|
|
1620
1619
|
function buildToolKey(serverName, toolName) {
|
|
1621
1620
|
return `mcp__${serverName}__${toolName}`;
|
|
1622
1621
|
}
|
|
1623
|
-
function
|
|
1624
|
-
return
|
|
1622
|
+
function delay(ms) {
|
|
1623
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1625
1624
|
}
|
|
1626
|
-
async function
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
await client.connect(transport);
|
|
1638
|
-
const result = await client.listTools();
|
|
1639
|
-
return result.tools;
|
|
1640
|
-
} finally {
|
|
1641
|
-
await client.close().catch(() => {
|
|
1642
|
-
});
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
function extractToolMetadata(tool) {
|
|
1646
|
-
return {
|
|
1647
|
-
readOnly: tool.annotations?.readOnlyHint === true
|
|
1648
|
-
};
|
|
1649
|
-
}
|
|
1650
|
-
async function fetchMcpToolMetadata(mcpServers, logger = new Logger({ debug: false, prefix: "[McpToolMetadata]" })) {
|
|
1651
|
-
const fetchPromises = [];
|
|
1652
|
-
for (const [serverName, config] of Object.entries(mcpServers)) {
|
|
1653
|
-
if (!isHttpMcpServer(config)) {
|
|
1654
|
-
continue;
|
|
1625
|
+
async function fetchMcpToolMetadata(q, logger = new Logger({ debug: false, prefix: "[McpToolMetadata]" })) {
|
|
1626
|
+
let retries = 0;
|
|
1627
|
+
while (retries <= PENDING_MAX_RETRIES) {
|
|
1628
|
+
let statuses;
|
|
1629
|
+
try {
|
|
1630
|
+
statuses = await q.mcpServerStatus();
|
|
1631
|
+
} catch (error) {
|
|
1632
|
+
logger.error("Failed to fetch MCP server status", {
|
|
1633
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1634
|
+
});
|
|
1635
|
+
return;
|
|
1655
1636
|
}
|
|
1656
|
-
const
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1637
|
+
const pendingServers = statuses.filter((s) => s.status === "pending");
|
|
1638
|
+
for (const server of statuses) {
|
|
1639
|
+
if (server.status !== "connected" || !server.tools) {
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
let readOnlyCount = 0;
|
|
1643
|
+
for (const tool of server.tools) {
|
|
1644
|
+
const toolKey = buildToolKey(server.name, tool.name);
|
|
1645
|
+
const readOnly = tool.annotations?.readOnly === true;
|
|
1646
|
+
mcpToolMetadataCache.set(toolKey, { readOnly });
|
|
1647
|
+
if (readOnly) readOnlyCount++;
|
|
1664
1648
|
}
|
|
1665
1649
|
logger.info("Fetched MCP tool metadata", {
|
|
1666
|
-
serverName,
|
|
1667
|
-
toolCount,
|
|
1650
|
+
serverName: server.name,
|
|
1651
|
+
toolCount: server.tools.length,
|
|
1668
1652
|
readOnlyCount
|
|
1669
1653
|
});
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1654
|
+
}
|
|
1655
|
+
if (pendingServers.length === 0) {
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
retries++;
|
|
1659
|
+
if (retries > PENDING_MAX_RETRIES) {
|
|
1660
|
+
logger.warn("Gave up waiting for pending MCP servers", {
|
|
1661
|
+
pendingServers: pendingServers.map((s) => s.name)
|
|
1674
1662
|
});
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
logger.info("Waiting for pending MCP servers", {
|
|
1666
|
+
pendingServers: pendingServers.map((s) => s.name),
|
|
1667
|
+
retry: retries
|
|
1675
1668
|
});
|
|
1676
|
-
|
|
1669
|
+
await delay(PENDING_RETRY_INTERVAL_MS);
|
|
1677
1670
|
}
|
|
1678
|
-
await Promise.all(fetchPromises);
|
|
1679
1671
|
}
|
|
1680
1672
|
function isMcpToolReadOnly(toolName) {
|
|
1681
1673
|
const metadata = mcpToolMetadataCache.get(toolName);
|
|
@@ -2308,7 +2300,8 @@ function buildEnvironment() {
|
|
|
2308
2300
|
...process.env,
|
|
2309
2301
|
ELECTRON_RUN_AS_NODE: "1",
|
|
2310
2302
|
CLAUDE_CODE_ENABLE_ASK_USER_QUESTION_TOOL: "true",
|
|
2311
|
-
|
|
2303
|
+
// Offload all MCP tools by default
|
|
2304
|
+
ENABLE_TOOL_SEARCH: "auto:0"
|
|
2312
2305
|
};
|
|
2313
2306
|
}
|
|
2314
2307
|
function buildHooks(userHooks, onModeChange) {
|
|
@@ -2547,7 +2540,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
2547
2540
|
});
|
|
2548
2541
|
}
|
|
2549
2542
|
const modelOptions = await this.getModelConfigOptions();
|
|
2550
|
-
this.deferBackgroundFetches(q, sessionId
|
|
2543
|
+
this.deferBackgroundFetches(q, sessionId);
|
|
2551
2544
|
session.modelId = modelOptions.currentModelId;
|
|
2552
2545
|
const resolvedSdkModel = toSdkModelId(modelOptions.currentModelId);
|
|
2553
2546
|
if (resolvedSdkModel !== DEFAULT_MODEL) {
|
|
@@ -2592,7 +2585,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
2592
2585
|
if (validation.result === "timeout") {
|
|
2593
2586
|
throw new Error("Session validation timed out");
|
|
2594
2587
|
}
|
|
2595
|
-
this.deferBackgroundFetches(q, sessionId
|
|
2588
|
+
this.deferBackgroundFetches(q, sessionId);
|
|
2596
2589
|
const configOptions = await this.buildConfigOptions();
|
|
2597
2590
|
return { configOptions };
|
|
2598
2591
|
}
|
|
@@ -2780,10 +2773,10 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
2780
2773
|
* Fire-and-forget: fetch slash commands and MCP tool metadata in parallel.
|
|
2781
2774
|
* Both populate caches used later — neither is needed to return configOptions.
|
|
2782
2775
|
*/
|
|
2783
|
-
deferBackgroundFetches(q, sessionId
|
|
2776
|
+
deferBackgroundFetches(q, sessionId) {
|
|
2784
2777
|
Promise.all([
|
|
2785
2778
|
getAvailableSlashCommands(q),
|
|
2786
|
-
fetchMcpToolMetadata(
|
|
2779
|
+
fetchMcpToolMetadata(q, this.logger)
|
|
2787
2780
|
]).then(([slashCommands]) => {
|
|
2788
2781
|
this.sendAvailableCommandsUpdate(sessionId, slashCommands);
|
|
2789
2782
|
}).catch((err) => {
|
|
@@ -2881,9 +2874,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
|
|
|
2881
2874
|
case "tool_use_summary":
|
|
2882
2875
|
return null;
|
|
2883
2876
|
default:
|
|
2884
|
-
|
|
2885
|
-
type: message.type
|
|
2886
|
-
});
|
|
2877
|
+
unreachable(message, this.logger);
|
|
2887
2878
|
return null;
|
|
2888
2879
|
}
|
|
2889
2880
|
}
|
|
@@ -3717,18 +3708,18 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
3717
3708
|
const retryCount = this.retryCounts.get(sessionId) ?? 0;
|
|
3718
3709
|
const lastAttempt = this.lastFlushAttemptTime.get(sessionId) ?? 0;
|
|
3719
3710
|
const elapsed = Date.now() - lastAttempt;
|
|
3720
|
-
let
|
|
3711
|
+
let delay2;
|
|
3721
3712
|
if (retryCount > 0) {
|
|
3722
|
-
|
|
3713
|
+
delay2 = Math.min(
|
|
3723
3714
|
_SessionLogWriter.FLUSH_DEBOUNCE_MS * 2 ** retryCount,
|
|
3724
3715
|
_SessionLogWriter.MAX_RETRY_DELAY_MS
|
|
3725
3716
|
);
|
|
3726
3717
|
} else if (elapsed >= _SessionLogWriter.FLUSH_MAX_INTERVAL_MS) {
|
|
3727
|
-
|
|
3718
|
+
delay2 = 0;
|
|
3728
3719
|
} else {
|
|
3729
|
-
|
|
3720
|
+
delay2 = _SessionLogWriter.FLUSH_DEBOUNCE_MS;
|
|
3730
3721
|
}
|
|
3731
|
-
const timeout = setTimeout(() => this.flush(sessionId),
|
|
3722
|
+
const timeout = setTimeout(() => this.flush(sessionId), delay2);
|
|
3732
3723
|
this.flushTimeouts.set(sessionId, timeout);
|
|
3733
3724
|
}
|
|
3734
3725
|
writeToLocalCache(sessionId, entry) {
|
|
@@ -3793,6 +3784,7 @@ var Agent = class {
|
|
|
3793
3784
|
}
|
|
3794
3785
|
async run(taskId, taskRunId, options = {}) {
|
|
3795
3786
|
const gatewayConfig = this._configureLlmGateway(options.adapter);
|
|
3787
|
+
this.logger.info("Configured LLM gateway", options);
|
|
3796
3788
|
this.taskRunId = taskRunId;
|
|
3797
3789
|
let allowedModelIds;
|
|
3798
3790
|
let sanitizedModel = options.model && !BLOCKED_MODELS.has(options.model) ? options.model : void 0;
|