@astrasyncai/verification-gateway 3.10.1 → 3.11.0

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 (57) hide show
  1. package/dist/adapters/express.js +1 -1
  2. package/dist/adapters/express.js.map +1 -1
  3. package/dist/adapters/express.mjs +1 -1
  4. package/dist/adapters/express.mjs.map +1 -1
  5. package/dist/adapters/mcp.js +1 -1
  6. package/dist/adapters/mcp.js.map +1 -1
  7. package/dist/adapters/mcp.mjs +1 -1
  8. package/dist/adapters/mcp.mjs.map +1 -1
  9. package/dist/adapters/nextjs.js +1 -1
  10. package/dist/adapters/nextjs.js.map +1 -1
  11. package/dist/adapters/nextjs.mjs +1 -1
  12. package/dist/adapters/nextjs.mjs.map +1 -1
  13. package/dist/adapters/sdk.js +1 -1
  14. package/dist/adapters/sdk.js.map +1 -1
  15. package/dist/adapters/sdk.mjs +1 -1
  16. package/dist/adapters/sdk.mjs.map +1 -1
  17. package/dist/bin/astrasync-claude-hook.js +47 -13
  18. package/dist/bin/astrasync-codex-hook.js +5107 -0
  19. package/dist/bin/astrasync-guard.js +176 -18
  20. package/dist/bin/astrasync.js +184 -18
  21. package/dist/browser/background.js +26 -3
  22. package/dist/browser/background.js.map +1 -1
  23. package/dist/browser/background.mjs +26 -3
  24. package/dist/browser/background.mjs.map +1 -1
  25. package/dist/claude-code/claude-code-adapter.js +9 -1
  26. package/dist/claude-code/claude-code-adapter.js.map +1 -1
  27. package/dist/claude-code/claude-code-adapter.mjs +9 -1
  28. package/dist/claude-code/claude-code-adapter.mjs.map +1 -1
  29. package/dist/codex/index.d.mts +230 -0
  30. package/dist/codex/index.d.ts +230 -0
  31. package/dist/codex/index.js +5225 -0
  32. package/dist/codex/index.js.map +1 -0
  33. package/dist/codex/index.mjs +5197 -0
  34. package/dist/codex/index.mjs.map +1 -0
  35. package/dist/cursor/extension.js +26 -3
  36. package/dist/cursor/extension.js.map +1 -1
  37. package/dist/cursor/extension.mjs +26 -3
  38. package/dist/cursor/extension.mjs.map +1 -1
  39. package/dist/gateway/gateway.d.mts +5 -0
  40. package/dist/gateway/gateway.d.ts +5 -0
  41. package/dist/gateway/gateway.js +26 -3
  42. package/dist/gateway/gateway.js.map +1 -1
  43. package/dist/gateway/gateway.mjs +26 -3
  44. package/dist/gateway/gateway.mjs.map +1 -1
  45. package/dist/index.d.mts +1 -1
  46. package/dist/index.d.ts +1 -1
  47. package/dist/index.js +1 -1
  48. package/dist/index.js.map +1 -1
  49. package/dist/index.mjs +1 -1
  50. package/dist/index.mjs.map +1 -1
  51. package/dist/local-evaluator/evaluator.js.map +1 -1
  52. package/dist/local-evaluator/evaluator.mjs.map +1 -1
  53. package/dist/verify.js +1 -1
  54. package/dist/verify.js.map +1 -1
  55. package/dist/verify.mjs +1 -1
  56. package/dist/verify.mjs.map +1 -1
  57. package/package.json +8 -2
@@ -114,6 +114,11 @@ function extractNetworkDomains(target) {
114
114
  // src/claude-code/tool-mapping.ts
115
115
  var CLAUDE_CODE_TOOL_MAP = {
116
116
  Bash: "shell.exec",
117
+ // Native Windows ships shell execution as a distinct PowerShell tool
118
+ // (hooks docs: "Tool Names in PreToolUse"). Found live on 10 Jul 2026 —
119
+ // the desktop-app runtime verification showed shell commands bypassing
120
+ // the guard entirely on Windows because only Bash was mapped/matched.
121
+ PowerShell: "shell.exec",
117
122
  Write: "file.write",
118
123
  Edit: "file.write",
119
124
  MultiEdit: "file.write",
@@ -258,7 +263,10 @@ var ClaudeCodeAdapter = class {
258
263
  timestamp: /* @__PURE__ */ new Date()
259
264
  };
260
265
  const interception = await this.interceptAction(action);
261
- if (!interception.intercepted || !interception.context) return null;
266
+ if (!interception.intercepted || !interception.context) {
267
+ this.gateway.logSkippedTool(payload.tool_name, interception.skipReason ?? "not intercepted");
268
+ return null;
269
+ }
262
270
  const decision = await this.gateway.evaluate(interception.context);
263
271
  return this.decisionToHookOutput(decision);
264
272
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/claude-code/claude-code-adapter.ts","../../src/adapter-interface/interface.ts","../../src/adapter-interface/purpose-mapping.ts","../../src/claude-code/tool-mapping.ts"],"sourcesContent":["/**\n * @astrasyncai/adapter-claude-code\n *\n * Layer 4 adapter for Claude Code (Anthropic's CLI coding agent).\n * Intercepts tool calls via Claude Code's native PreToolUse hook:\n * the hook binary receives {tool_name, tool_input, ...} on stdin and\n * answers with a permissionDecision on stdout.\n *\n * Decision mapping is strictly tightening:\n * - DENY → permissionDecision \"deny\"\n * - MANUAL_REVIEW → permissionDecision \"ask\" (Claude Code's own permission\n * prompt is the human-in-the-loop)\n * - ALLOW → no output at all — the guard NEVER emits \"allow\",\n * because \"allow\" would bypass Claude Code's native\n * permission system rather than defer to it.\n *\n * ~250 lines — thin, disposable, platform-specific.\n */\n\nimport type { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface';\nimport type {\n PDLSSContext,\n VerificationDecision,\n AgentAction,\n InterceptResult,\n} from '../gateway/types';\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport { ADAPTER_INTERFACE_VERSION } from '../adapter-interface/interface';\nimport {\n mapToolToPurpose,\n extractTarget,\n extractNetworkDomains,\n} from '../adapter-interface/purpose-mapping';\nimport { registerClaudeCodeMappings, mapMcpTool, CLAUDE_CODE_TOOL_MAP } from './tool-mapping';\n\n// -----------------------------------------------------------------------\n// Claude Code hook payload / output shapes\n// (contract: https://code.claude.com/docs/en/hooks.md)\n// -----------------------------------------------------------------------\n\n/** The PreToolUse payload Claude Code writes to the hook's stdin. */\nexport interface ClaudeCodePreToolUsePayload {\n hook_event_name?: string;\n tool_name: string;\n tool_input: Record<string, unknown>;\n session_id?: string;\n cwd?: string;\n transcript_path?: string;\n permission_mode?: string;\n}\n\n/** The JSON the hook prints to stdout when it wants to tighten a decision. */\nexport interface ClaudeHookOutput {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse';\n permissionDecision: 'deny' | 'ask';\n permissionDecisionReason: string;\n };\n}\n\n// -----------------------------------------------------------------------\n// Configuration\n// -----------------------------------------------------------------------\n\n/**\n * Tools that are never governed: session bookkeeping and UI-only tools\n * with no side effects outside the conversation.\n */\nexport const DEFAULT_SKIP_TOOLS = [\n 'TodoWrite',\n 'TaskCreate',\n 'TaskUpdate',\n 'TaskList',\n 'TaskGet',\n 'ExitPlanMode',\n 'EnterPlanMode',\n 'AskUserQuestion',\n 'BashOutput',\n 'KillShell',\n 'TaskStop',\n 'TaskOutput',\n 'ListMcpResourcesTool',\n];\n\nexport interface ClaudeCodeAdapterOptions {\n /** Tool names to never intercept (replaces DEFAULT_SKIP_TOOLS if given) */\n skipTools?: string[];\n /**\n * When true, unmapped non-MCP tools are still intercepted (purpose\n * `tool.<name>`, denied by default policy). When false (default),\n * unmapped tools are skipped — Claude Code's own permission system\n * remains their only gate.\n */\n strictUnmapped?: boolean;\n /**\n * Govern MCP tools (mcp__{server}__{tool} → purpose {server}.{tool}).\n * Defaults to true. Note the evaluator denies purposes missing from the\n * policy, so with governMcp on, every MCP server in use needs purpose\n * rules; installers may default this off until the policy lists them.\n */\n governMcp?: boolean;\n}\n\n// -----------------------------------------------------------------------\n// Adapter implementation\n// -----------------------------------------------------------------------\n\nexport class ClaudeCodeAdapter implements PlatformAdapter {\n readonly interfaceVersion = ADAPTER_INTERFACE_VERSION;\n\n private gateway!: AstraSyncGateway;\n private skipTools: Set<string>;\n private strictUnmapped: boolean;\n private governMcp: boolean;\n private _isRunning = false;\n\n constructor(options?: ClaudeCodeAdapterOptions) {\n this.skipTools = new Set(options?.skipTools ?? DEFAULT_SKIP_TOOLS);\n this.strictUnmapped = options?.strictUnmapped ?? false;\n this.governMcp = options?.governMcp ?? true;\n registerClaudeCodeMappings();\n }\n\n get isRunning(): boolean {\n return this._isRunning;\n }\n\n async initialize(config: AdapterConfig): Promise<void> {\n this.gateway = config.gateway as AstraSyncGateway;\n\n if (Array.isArray(config.adapterOptions.skipTools)) {\n this.skipTools = new Set(config.adapterOptions.skipTools as string[]);\n }\n if (typeof config.adapterOptions.strictUnmapped === 'boolean') {\n this.strictUnmapped = config.adapterOptions.strictUnmapped;\n }\n\n this._isRunning = true;\n }\n\n async shutdown(): Promise<void> {\n this._isRunning = false;\n }\n\n async interceptAction(action: AgentAction): Promise<InterceptResult> {\n const raw = action.raw as Partial<ClaudeCodePreToolUsePayload> | null;\n\n if (!raw || typeof raw.tool_name !== 'string' || raw.tool_name.length === 0) {\n return { intercepted: false, skipReason: 'No tool_name in payload' };\n }\n\n if (this.skipTools.has(raw.tool_name)) {\n return { intercepted: false, skipReason: `Tool ${raw.tool_name} is not governed` };\n }\n\n const isMcp = mapMcpTool(raw.tool_name) !== null;\n if (isMcp && !this.governMcp) {\n return { intercepted: false, skipReason: `MCP tools not governed (governMcp off)` };\n }\n\n const isMapped = raw.tool_name in CLAUDE_CODE_TOOL_MAP || isMcp;\n if (!isMapped && !this.strictUnmapped) {\n return {\n intercepted: false,\n skipReason: `Unmapped tool ${raw.tool_name} (strictUnmapped off)`,\n };\n }\n\n const context = this.extractContext(action);\n return { intercepted: true, context };\n }\n\n extractContext(action: AgentAction): PDLSSContext {\n const raw = action.raw as ClaudeCodePreToolUsePayload;\n const toolName = raw.tool_name;\n const args = (raw.tool_input ?? {}) as Record<string, unknown>;\n\n const mcpPurpose = mapMcpTool(toolName);\n const purpose = mcpPurpose ?? mapToolToPurpose(toolName);\n const target = extractTarget(toolName, args);\n const networkAccess = extractNetworkDomains(target);\n\n return {\n purpose,\n action: toolName,\n target,\n ...(networkAccess && { networkAccess }),\n metadata: {\n ...(raw.session_id && { sessionId: raw.session_id }),\n ...(raw.cwd && { cwd: raw.cwd }),\n platform: 'claude-code',\n },\n };\n }\n\n /**\n * Translate a gateway decision into hook stdout JSON.\n * Returns null on ALLOW — the hook stays silent and Claude Code's own\n * permission system remains in charge (strictly-tightening invariant).\n */\n decisionToHookOutput(decision: VerificationDecision): ClaudeHookOutput | null {\n switch (decision.recommendation) {\n case 'DENY':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: `AstraSync Local Guard: ${decision.reason}`,\n },\n };\n case 'MANUAL_REVIEW':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'ask',\n permissionDecisionReason: `AstraSync Local Guard requires approval: ${decision.reason}`,\n },\n };\n case 'ALLOW':\n return null;\n }\n }\n\n /**\n * Convenience for the hook entry: full payload → hook output (or null).\n */\n async evaluatePayload(payload: ClaudeCodePreToolUsePayload): Promise<ClaudeHookOutput | null> {\n const action: AgentAction = {\n raw: payload,\n platform: 'claude-code',\n timestamp: new Date(),\n };\n\n const interception = await this.interceptAction(action);\n if (!interception.intercepted || !interception.context) return null;\n\n const decision = await this.gateway.evaluate(interception.context);\n return this.decisionToHookOutput(decision);\n }\n\n /**\n * Enforcement happens via the stdout JSON the hook prints (see\n * decisionToHookOutput); there is no in-process side to enforce on.\n */\n async enforceDecision(_decision: VerificationDecision): Promise<void> {\n // Contract no-op: Claude Code enforces based on the hook's stdout.\n }\n}\n","/**\n * PlatformAdapter Interface\n *\n * The contract that Layer 4 platform adapters implement.\n * Agent-side interception: governs what agents are allowed to do before they do it.\n *\n * Each adapter is 200-500 lines of platform-specific code.\n * All verification logic lives in the gateway — adapters just translate\n * between the platform's world and AstraSync's world.\n */\n\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport type { PDLSSContext, VerificationDecision, AgentAction, InterceptResult } from '../gateway/types';\n\nexport interface AdapterConfig {\n /** The AstraSyncGateway instance (handles mode routing) */\n gateway: AstraSyncGateway;\n /** Platform-specific configuration */\n adapterOptions: Record<string, unknown>;\n}\n\nexport interface PlatformAdapter {\n /**\n * Interface version for compatibility checking.\n * Current version: 1.\n */\n readonly interfaceVersion: number;\n\n /**\n * Platform-specific initialization.\n * Load config, register hooks, establish connections.\n */\n initialize(config: AdapterConfig): Promise<void>;\n\n /**\n * Graceful shutdown.\n * Drain in-flight verifications, deregister hooks, close connections.\n */\n shutdown(): Promise<void>;\n\n /**\n * Intercept an agent action before execution.\n *\n * How this works depends on the platform:\n * - CLI adapter: proxy server captures outbound request\n * - Browser adapter: content script intercepts DOM interaction\n * - Express: middleware captures inbound request\n */\n interceptAction(action: AgentAction): Promise<InterceptResult>;\n\n /**\n * Extract PDLSS-compatible context from a platform-specific action.\n * Maps platform-native action format to the universal PDLSSContext.\n */\n extractContext(action: AgentAction): PDLSSContext;\n\n /**\n * Enforce the verification decision in a platform-specific way.\n *\n * How this works depends on the platform:\n * - CLI: block command / allow command / prompt for approval\n * - Browser: block navigation / show confirmation dialog\n * - Express: return 403 / pass through / inject headers\n */\n enforceDecision(decision: VerificationDecision): Promise<void>;\n}\n\n/**\n * Current adapter interface version.\n */\nexport const ADAPTER_INTERFACE_VERSION = 1;\n\n/**\n * Check if an adapter is compatible with the current interface version.\n */\nexport function isCompatibleAdapter(adapter: PlatformAdapter): boolean {\n return adapter.interfaceVersion === ADAPTER_INTERFACE_VERSION;\n}\n","/**\n * Shared purpose mapping utilities for Layer 4 platform adapters.\n *\n * Maps platform-native action names to PDLSS purpose categories.\n * Used by OpenClaw CLI, Cursor, browser, and future adapters.\n */\n\n// -----------------------------------------------------------------------\n// Tool → Purpose mapping\n// -----------------------------------------------------------------------\n\n/** Standard tool name → PDLSS purpose mapping used by all adapters */\nconst TOOL_PURPOSE_MAP: Record<string, string> = {\n // Shell\n shell_exec: 'shell.exec',\n run_command: 'shell.exec',\n execute: 'shell.exec',\n terminal_exec: 'shell.exec',\n run_terminal_command: 'shell.exec',\n\n // File read\n file_read: 'file.read',\n read_file: 'file.read',\n\n // File write\n file_write: 'file.write',\n write_file: 'file.write',\n create_file: 'file.write',\n edit_file: 'file.write',\n\n // File delete\n file_delete: 'file.delete',\n delete_file: 'file.delete',\n\n // Network\n http_request: 'network.request',\n fetch: 'network.request',\n web_request: 'network.request',\n\n // Email\n send_email: 'email.send',\n read_email: 'email.read',\n\n // Calendar\n create_event: 'calendar.create',\n\n // Database\n query_database: 'database.query',\n write_database: 'database.write',\n\n // Payment\n payment_execute: 'payment.execute',\n};\n\n/**\n * Map a tool/action name to a PDLSS purpose category.\n * Returns `tool.<name>` for unmapped tools (denied by default).\n */\nexport function mapToolToPurpose(toolName: string): string {\n return TOOL_PURPOSE_MAP[toolName] || `tool.${toolName}`;\n}\n\n/**\n * Register additional tool → purpose mappings (e.g. from a platform adapter).\n * Does not overwrite existing mappings.\n */\nexport function registerToolMappings(mappings: Record<string, string>): void {\n for (const [tool, purpose] of Object.entries(mappings)) {\n if (!(tool in TOOL_PURPOSE_MAP)) {\n TOOL_PURPOSE_MAP[tool] = purpose;\n }\n }\n}\n\n// -----------------------------------------------------------------------\n// Target extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract the meaningful target string from tool arguments.\n * Uses the purpose category to determine which argument field is relevant.\n */\nexport function extractTarget(toolName: string, args: Record<string, unknown>): string {\n const purpose = mapToolToPurpose(toolName);\n\n if (purpose.startsWith('shell.')) {\n return String(args.command || args.cmd || args.script || '');\n }\n\n if (purpose.startsWith('file.')) {\n return String(args.path || args.file || args.filename || args.file_path || '');\n }\n\n if (purpose.startsWith('network.')) {\n return String(args.url || args.endpoint || args.uri || '');\n }\n\n if (purpose.startsWith('email.')) {\n return String(args.to || args.recipient || args.address || '');\n }\n\n if (purpose.startsWith('database.')) {\n return String(args.query || args.table || '');\n }\n\n if (purpose.startsWith('payment.')) {\n return String(args.description || args.merchant || args.amount || '');\n }\n\n // Fallback: try common field names\n if (args.command) return String(args.command);\n if (args.path) return String(args.path);\n if (args.url) return String(args.url);\n\n // Default: use first non-empty string argument or tool name\n for (const val of Object.values(args)) {\n if (typeof val === 'string' && val.length > 0) return val;\n }\n return toolName;\n}\n\n// -----------------------------------------------------------------------\n// Network domain extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract network domains from a URL target.\n * Returns undefined if the target is not a URL.\n */\nexport function extractNetworkDomains(target: string): string[] | undefined {\n try {\n if (target.startsWith('http://') || target.startsWith('https://')) {\n const url = new URL(target);\n return [url.hostname];\n }\n } catch {\n // Not a URL\n }\n return undefined;\n}\n","/**\n * @astrasyncai/adapter-claude-code — tool → purpose mapping\n *\n * Maps Claude Code's built-in tool names (as delivered in PreToolUse hook\n * payloads) to PDLSS purpose categories, and parses MCP tool names\n * (mcp__{server}__{tool}) into app-connector style purposes.\n */\n\nimport { registerToolMappings } from '../adapter-interface/purpose-mapping';\n\n/** Claude Code built-in tool name → PDLSS purpose */\nexport const CLAUDE_CODE_TOOL_MAP: Record<string, string> = {\n Bash: 'shell.exec',\n Write: 'file.write',\n Edit: 'file.write',\n MultiEdit: 'file.write',\n NotebookEdit: 'file.write',\n Read: 'file.read',\n Glob: 'file.read',\n Grep: 'file.read',\n WebFetch: 'network.request',\n WebSearch: 'network.request',\n Task: 'sub_agent.spawn',\n Agent: 'sub_agent.spawn',\n};\n\nlet registered = false;\n\n/**\n * Register the Claude Code mappings into the shared adapter purpose map.\n * Idempotent; safe to call from both the adapter and the hook entry.\n */\nexport function registerClaudeCodeMappings(): void {\n if (registered) return;\n registerToolMappings(CLAUDE_CODE_TOOL_MAP);\n registered = true;\n}\n\n/**\n * Parse an MCP tool name (mcp__{server}__{tool}) into an app-connector\n * style purpose `{server}.{tool}`. Returns null for non-MCP tool names.\n */\nexport function mapMcpTool(toolName: string): string | null {\n const match = /^mcp__(.+?)__(.+)$/.exec(toolName);\n if (!match) return null;\n return `${match[1]}.${match[2]}`;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/claude-code/claude-code-adapter.ts","../../src/adapter-interface/interface.ts","../../src/adapter-interface/purpose-mapping.ts","../../src/claude-code/tool-mapping.ts"],"sourcesContent":["/**\n * @astrasyncai/adapter-claude-code\n *\n * Layer 4 adapter for Claude Code (Anthropic's CLI coding agent).\n * Intercepts tool calls via Claude Code's native PreToolUse hook:\n * the hook binary receives {tool_name, tool_input, ...} on stdin and\n * answers with a permissionDecision on stdout.\n *\n * Decision mapping is strictly tightening:\n * - DENY → permissionDecision \"deny\"\n * - MANUAL_REVIEW → permissionDecision \"ask\" (Claude Code's own permission\n * prompt is the human-in-the-loop)\n * - ALLOW → no output at all — the guard NEVER emits \"allow\",\n * because \"allow\" would bypass Claude Code's native\n * permission system rather than defer to it.\n *\n * ~250 lines — thin, disposable, platform-specific.\n */\n\nimport type { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface';\nimport type {\n PDLSSContext,\n VerificationDecision,\n AgentAction,\n InterceptResult,\n} from '../gateway/types';\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport { ADAPTER_INTERFACE_VERSION } from '../adapter-interface/interface';\nimport {\n mapToolToPurpose,\n extractTarget,\n extractNetworkDomains,\n} from '../adapter-interface/purpose-mapping';\nimport { registerClaudeCodeMappings, mapMcpTool, CLAUDE_CODE_TOOL_MAP } from './tool-mapping';\n\n// -----------------------------------------------------------------------\n// Claude Code hook payload / output shapes\n// (contract: https://code.claude.com/docs/en/hooks.md)\n// -----------------------------------------------------------------------\n\n/** The PreToolUse payload Claude Code writes to the hook's stdin. */\nexport interface ClaudeCodePreToolUsePayload {\n hook_event_name?: string;\n tool_name: string;\n tool_input: Record<string, unknown>;\n session_id?: string;\n cwd?: string;\n transcript_path?: string;\n permission_mode?: string;\n}\n\n/** The JSON the hook prints to stdout when it wants to tighten a decision. */\nexport interface ClaudeHookOutput {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse';\n permissionDecision: 'deny' | 'ask';\n permissionDecisionReason: string;\n };\n}\n\n// -----------------------------------------------------------------------\n// Configuration\n// -----------------------------------------------------------------------\n\n/**\n * Tools that are never governed: session bookkeeping and UI-only tools\n * with no side effects outside the conversation.\n */\nexport const DEFAULT_SKIP_TOOLS = [\n 'TodoWrite',\n 'TaskCreate',\n 'TaskUpdate',\n 'TaskList',\n 'TaskGet',\n 'ExitPlanMode',\n 'EnterPlanMode',\n 'AskUserQuestion',\n 'BashOutput',\n 'KillShell',\n 'TaskStop',\n 'TaskOutput',\n 'ListMcpResourcesTool',\n];\n\nexport interface ClaudeCodeAdapterOptions {\n /** Tool names to never intercept (replaces DEFAULT_SKIP_TOOLS if given) */\n skipTools?: string[];\n /**\n * When true, unmapped non-MCP tools are still intercepted (purpose\n * `tool.<name>`, denied by default policy). When false (default),\n * unmapped tools are skipped — Claude Code's own permission system\n * remains their only gate.\n */\n strictUnmapped?: boolean;\n /**\n * Govern MCP tools (mcp__{server}__{tool} → purpose {server}.{tool}).\n * Defaults to true. Note the evaluator denies purposes missing from the\n * policy, so with governMcp on, every MCP server in use needs purpose\n * rules; installers may default this off until the policy lists them.\n */\n governMcp?: boolean;\n}\n\n// -----------------------------------------------------------------------\n// Adapter implementation\n// -----------------------------------------------------------------------\n\nexport class ClaudeCodeAdapter implements PlatformAdapter {\n readonly interfaceVersion = ADAPTER_INTERFACE_VERSION;\n\n private gateway!: AstraSyncGateway;\n private skipTools: Set<string>;\n private strictUnmapped: boolean;\n private governMcp: boolean;\n private _isRunning = false;\n\n constructor(options?: ClaudeCodeAdapterOptions) {\n this.skipTools = new Set(options?.skipTools ?? DEFAULT_SKIP_TOOLS);\n this.strictUnmapped = options?.strictUnmapped ?? false;\n this.governMcp = options?.governMcp ?? true;\n registerClaudeCodeMappings();\n }\n\n get isRunning(): boolean {\n return this._isRunning;\n }\n\n async initialize(config: AdapterConfig): Promise<void> {\n this.gateway = config.gateway as AstraSyncGateway;\n\n if (Array.isArray(config.adapterOptions.skipTools)) {\n this.skipTools = new Set(config.adapterOptions.skipTools as string[]);\n }\n if (typeof config.adapterOptions.strictUnmapped === 'boolean') {\n this.strictUnmapped = config.adapterOptions.strictUnmapped;\n }\n\n this._isRunning = true;\n }\n\n async shutdown(): Promise<void> {\n this._isRunning = false;\n }\n\n async interceptAction(action: AgentAction): Promise<InterceptResult> {\n const raw = action.raw as Partial<ClaudeCodePreToolUsePayload> | null;\n\n if (!raw || typeof raw.tool_name !== 'string' || raw.tool_name.length === 0) {\n return { intercepted: false, skipReason: 'No tool_name in payload' };\n }\n\n if (this.skipTools.has(raw.tool_name)) {\n return { intercepted: false, skipReason: `Tool ${raw.tool_name} is not governed` };\n }\n\n const isMcp = mapMcpTool(raw.tool_name) !== null;\n if (isMcp && !this.governMcp) {\n return { intercepted: false, skipReason: `MCP tools not governed (governMcp off)` };\n }\n\n const isMapped = raw.tool_name in CLAUDE_CODE_TOOL_MAP || isMcp;\n if (!isMapped && !this.strictUnmapped) {\n return {\n intercepted: false,\n skipReason: `Unmapped tool ${raw.tool_name} (strictUnmapped off)`,\n };\n }\n\n const context = this.extractContext(action);\n return { intercepted: true, context };\n }\n\n extractContext(action: AgentAction): PDLSSContext {\n const raw = action.raw as ClaudeCodePreToolUsePayload;\n const toolName = raw.tool_name;\n const args = (raw.tool_input ?? {}) as Record<string, unknown>;\n\n const mcpPurpose = mapMcpTool(toolName);\n const purpose = mcpPurpose ?? mapToolToPurpose(toolName);\n const target = extractTarget(toolName, args);\n const networkAccess = extractNetworkDomains(target);\n\n return {\n purpose,\n action: toolName,\n target,\n ...(networkAccess && { networkAccess }),\n metadata: {\n ...(raw.session_id && { sessionId: raw.session_id }),\n ...(raw.cwd && { cwd: raw.cwd }),\n platform: 'claude-code',\n },\n };\n }\n\n /**\n * Translate a gateway decision into hook stdout JSON.\n * Returns null on ALLOW — the hook stays silent and Claude Code's own\n * permission system remains in charge (strictly-tightening invariant).\n */\n decisionToHookOutput(decision: VerificationDecision): ClaudeHookOutput | null {\n switch (decision.recommendation) {\n case 'DENY':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: `AstraSync Local Guard: ${decision.reason}`,\n },\n };\n case 'MANUAL_REVIEW':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'ask',\n permissionDecisionReason: `AstraSync Local Guard requires approval: ${decision.reason}`,\n },\n };\n case 'ALLOW':\n return null;\n }\n }\n\n /**\n * Convenience for the hook entry: full payload → hook output (or null).\n */\n async evaluatePayload(payload: ClaudeCodePreToolUsePayload): Promise<ClaudeHookOutput | null> {\n const action: AgentAction = {\n raw: payload,\n platform: 'claude-code',\n timestamp: new Date(),\n };\n\n const interception = await this.interceptAction(action);\n if (!interception.intercepted || !interception.context) {\n // Visible skip: an ungoverned tool must be distinguishable from an\n // ALLOW in the traces (a silent skip hid the Windows PowerShell\n // coverage gap for a full passive week).\n this.gateway.logSkippedTool(payload.tool_name, interception.skipReason ?? 'not intercepted');\n return null;\n }\n\n const decision = await this.gateway.evaluate(interception.context);\n return this.decisionToHookOutput(decision);\n }\n\n /**\n * Enforcement happens via the stdout JSON the hook prints (see\n * decisionToHookOutput); there is no in-process side to enforce on.\n */\n async enforceDecision(_decision: VerificationDecision): Promise<void> {\n // Contract no-op: Claude Code enforces based on the hook's stdout.\n }\n}\n","/**\n * PlatformAdapter Interface\n *\n * The contract that Layer 4 platform adapters implement.\n * Agent-side interception: governs what agents are allowed to do before they do it.\n *\n * Each adapter is 200-500 lines of platform-specific code.\n * All verification logic lives in the gateway — adapters just translate\n * between the platform's world and AstraSync's world.\n */\n\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport type { PDLSSContext, VerificationDecision, AgentAction, InterceptResult } from '../gateway/types';\n\nexport interface AdapterConfig {\n /** The AstraSyncGateway instance (handles mode routing) */\n gateway: AstraSyncGateway;\n /** Platform-specific configuration */\n adapterOptions: Record<string, unknown>;\n}\n\nexport interface PlatformAdapter {\n /**\n * Interface version for compatibility checking.\n * Current version: 1.\n */\n readonly interfaceVersion: number;\n\n /**\n * Platform-specific initialization.\n * Load config, register hooks, establish connections.\n */\n initialize(config: AdapterConfig): Promise<void>;\n\n /**\n * Graceful shutdown.\n * Drain in-flight verifications, deregister hooks, close connections.\n */\n shutdown(): Promise<void>;\n\n /**\n * Intercept an agent action before execution.\n *\n * How this works depends on the platform:\n * - CLI adapter: proxy server captures outbound request\n * - Browser adapter: content script intercepts DOM interaction\n * - Express: middleware captures inbound request\n */\n interceptAction(action: AgentAction): Promise<InterceptResult>;\n\n /**\n * Extract PDLSS-compatible context from a platform-specific action.\n * Maps platform-native action format to the universal PDLSSContext.\n */\n extractContext(action: AgentAction): PDLSSContext;\n\n /**\n * Enforce the verification decision in a platform-specific way.\n *\n * How this works depends on the platform:\n * - CLI: block command / allow command / prompt for approval\n * - Browser: block navigation / show confirmation dialog\n * - Express: return 403 / pass through / inject headers\n */\n enforceDecision(decision: VerificationDecision): Promise<void>;\n}\n\n/**\n * Current adapter interface version.\n */\nexport const ADAPTER_INTERFACE_VERSION = 1;\n\n/**\n * Check if an adapter is compatible with the current interface version.\n */\nexport function isCompatibleAdapter(adapter: PlatformAdapter): boolean {\n return adapter.interfaceVersion === ADAPTER_INTERFACE_VERSION;\n}\n","/**\n * Shared purpose mapping utilities for Layer 4 platform adapters.\n *\n * Maps platform-native action names to PDLSS purpose categories.\n * Used by OpenClaw CLI, Cursor, browser, and future adapters.\n */\n\n// -----------------------------------------------------------------------\n// Tool → Purpose mapping\n// -----------------------------------------------------------------------\n\n/** Standard tool name → PDLSS purpose mapping used by all adapters */\nconst TOOL_PURPOSE_MAP: Record<string, string> = {\n // Shell\n shell_exec: 'shell.exec',\n run_command: 'shell.exec',\n execute: 'shell.exec',\n terminal_exec: 'shell.exec',\n run_terminal_command: 'shell.exec',\n\n // File read\n file_read: 'file.read',\n read_file: 'file.read',\n\n // File write\n file_write: 'file.write',\n write_file: 'file.write',\n create_file: 'file.write',\n edit_file: 'file.write',\n\n // File delete\n file_delete: 'file.delete',\n delete_file: 'file.delete',\n\n // Network\n http_request: 'network.request',\n fetch: 'network.request',\n web_request: 'network.request',\n\n // Email\n send_email: 'email.send',\n read_email: 'email.read',\n\n // Calendar\n create_event: 'calendar.create',\n\n // Database\n query_database: 'database.query',\n write_database: 'database.write',\n\n // Payment\n payment_execute: 'payment.execute',\n};\n\n/**\n * Map a tool/action name to a PDLSS purpose category.\n * Returns `tool.<name>` for unmapped tools (denied by default).\n */\nexport function mapToolToPurpose(toolName: string): string {\n return TOOL_PURPOSE_MAP[toolName] || `tool.${toolName}`;\n}\n\n/**\n * Register additional tool → purpose mappings (e.g. from a platform adapter).\n * Does not overwrite existing mappings.\n */\nexport function registerToolMappings(mappings: Record<string, string>): void {\n for (const [tool, purpose] of Object.entries(mappings)) {\n if (!(tool in TOOL_PURPOSE_MAP)) {\n TOOL_PURPOSE_MAP[tool] = purpose;\n }\n }\n}\n\n// -----------------------------------------------------------------------\n// Target extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract the meaningful target string from tool arguments.\n * Uses the purpose category to determine which argument field is relevant.\n */\nexport function extractTarget(toolName: string, args: Record<string, unknown>): string {\n const purpose = mapToolToPurpose(toolName);\n\n if (purpose.startsWith('shell.')) {\n return String(args.command || args.cmd || args.script || '');\n }\n\n if (purpose.startsWith('file.')) {\n return String(args.path || args.file || args.filename || args.file_path || '');\n }\n\n if (purpose.startsWith('network.')) {\n return String(args.url || args.endpoint || args.uri || '');\n }\n\n if (purpose.startsWith('email.')) {\n return String(args.to || args.recipient || args.address || '');\n }\n\n if (purpose.startsWith('database.')) {\n return String(args.query || args.table || '');\n }\n\n if (purpose.startsWith('payment.')) {\n return String(args.description || args.merchant || args.amount || '');\n }\n\n // Fallback: try common field names\n if (args.command) return String(args.command);\n if (args.path) return String(args.path);\n if (args.url) return String(args.url);\n\n // Default: use first non-empty string argument or tool name\n for (const val of Object.values(args)) {\n if (typeof val === 'string' && val.length > 0) return val;\n }\n return toolName;\n}\n\n// -----------------------------------------------------------------------\n// Network domain extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract network domains from a URL target.\n * Returns undefined if the target is not a URL.\n */\nexport function extractNetworkDomains(target: string): string[] | undefined {\n try {\n if (target.startsWith('http://') || target.startsWith('https://')) {\n const url = new URL(target);\n return [url.hostname];\n }\n } catch {\n // Not a URL\n }\n return undefined;\n}\n","/**\n * @astrasyncai/adapter-claude-code — tool → purpose mapping\n *\n * Maps Claude Code's built-in tool names (as delivered in PreToolUse hook\n * payloads) to PDLSS purpose categories, and parses MCP tool names\n * (mcp__{server}__{tool}) into app-connector style purposes.\n */\n\nimport { registerToolMappings } from '../adapter-interface/purpose-mapping';\n\n/** Claude Code built-in tool name → PDLSS purpose */\nexport const CLAUDE_CODE_TOOL_MAP: Record<string, string> = {\n Bash: 'shell.exec',\n // Native Windows ships shell execution as a distinct PowerShell tool\n // (hooks docs: \"Tool Names in PreToolUse\"). Found live on 10 Jul 2026 —\n // the desktop-app runtime verification showed shell commands bypassing\n // the guard entirely on Windows because only Bash was mapped/matched.\n PowerShell: 'shell.exec',\n Write: 'file.write',\n Edit: 'file.write',\n MultiEdit: 'file.write',\n NotebookEdit: 'file.write',\n Read: 'file.read',\n Glob: 'file.read',\n Grep: 'file.read',\n WebFetch: 'network.request',\n WebSearch: 'network.request',\n Task: 'sub_agent.spawn',\n Agent: 'sub_agent.spawn',\n};\n\nlet registered = false;\n\n/**\n * Register the Claude Code mappings into the shared adapter purpose map.\n * Idempotent; safe to call from both the adapter and the hook entry.\n */\nexport function registerClaudeCodeMappings(): void {\n if (registered) return;\n registerToolMappings(CLAUDE_CODE_TOOL_MAP);\n registered = true;\n}\n\n/**\n * Parse an MCP tool name (mcp__{server}__{tool}) into an app-connector\n * style purpose `{server}.{tool}`. Returns null for non-MCP tool names.\n */\nexport function mapMcpTool(toolName: string): string | null {\n const match = /^mcp__(.+?)__(.+)$/.exec(toolName);\n if (!match) return null;\n return `${match[1]}.${match[2]}`;\n}\n"],"mappings":"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","names":[]}
@@ -87,6 +87,11 @@ function extractNetworkDomains(target) {
87
87
  // src/claude-code/tool-mapping.ts
88
88
  var CLAUDE_CODE_TOOL_MAP = {
89
89
  Bash: "shell.exec",
90
+ // Native Windows ships shell execution as a distinct PowerShell tool
91
+ // (hooks docs: "Tool Names in PreToolUse"). Found live on 10 Jul 2026 —
92
+ // the desktop-app runtime verification showed shell commands bypassing
93
+ // the guard entirely on Windows because only Bash was mapped/matched.
94
+ PowerShell: "shell.exec",
90
95
  Write: "file.write",
91
96
  Edit: "file.write",
92
97
  MultiEdit: "file.write",
@@ -231,7 +236,10 @@ var ClaudeCodeAdapter = class {
231
236
  timestamp: /* @__PURE__ */ new Date()
232
237
  };
233
238
  const interception = await this.interceptAction(action);
234
- if (!interception.intercepted || !interception.context) return null;
239
+ if (!interception.intercepted || !interception.context) {
240
+ this.gateway.logSkippedTool(payload.tool_name, interception.skipReason ?? "not intercepted");
241
+ return null;
242
+ }
235
243
  const decision = await this.gateway.evaluate(interception.context);
236
244
  return this.decisionToHookOutput(decision);
237
245
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapter-interface/interface.ts","../../src/adapter-interface/purpose-mapping.ts","../../src/claude-code/tool-mapping.ts","../../src/claude-code/claude-code-adapter.ts"],"sourcesContent":["/**\n * PlatformAdapter Interface\n *\n * The contract that Layer 4 platform adapters implement.\n * Agent-side interception: governs what agents are allowed to do before they do it.\n *\n * Each adapter is 200-500 lines of platform-specific code.\n * All verification logic lives in the gateway — adapters just translate\n * between the platform's world and AstraSync's world.\n */\n\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport type { PDLSSContext, VerificationDecision, AgentAction, InterceptResult } from '../gateway/types';\n\nexport interface AdapterConfig {\n /** The AstraSyncGateway instance (handles mode routing) */\n gateway: AstraSyncGateway;\n /** Platform-specific configuration */\n adapterOptions: Record<string, unknown>;\n}\n\nexport interface PlatformAdapter {\n /**\n * Interface version for compatibility checking.\n * Current version: 1.\n */\n readonly interfaceVersion: number;\n\n /**\n * Platform-specific initialization.\n * Load config, register hooks, establish connections.\n */\n initialize(config: AdapterConfig): Promise<void>;\n\n /**\n * Graceful shutdown.\n * Drain in-flight verifications, deregister hooks, close connections.\n */\n shutdown(): Promise<void>;\n\n /**\n * Intercept an agent action before execution.\n *\n * How this works depends on the platform:\n * - CLI adapter: proxy server captures outbound request\n * - Browser adapter: content script intercepts DOM interaction\n * - Express: middleware captures inbound request\n */\n interceptAction(action: AgentAction): Promise<InterceptResult>;\n\n /**\n * Extract PDLSS-compatible context from a platform-specific action.\n * Maps platform-native action format to the universal PDLSSContext.\n */\n extractContext(action: AgentAction): PDLSSContext;\n\n /**\n * Enforce the verification decision in a platform-specific way.\n *\n * How this works depends on the platform:\n * - CLI: block command / allow command / prompt for approval\n * - Browser: block navigation / show confirmation dialog\n * - Express: return 403 / pass through / inject headers\n */\n enforceDecision(decision: VerificationDecision): Promise<void>;\n}\n\n/**\n * Current adapter interface version.\n */\nexport const ADAPTER_INTERFACE_VERSION = 1;\n\n/**\n * Check if an adapter is compatible with the current interface version.\n */\nexport function isCompatibleAdapter(adapter: PlatformAdapter): boolean {\n return adapter.interfaceVersion === ADAPTER_INTERFACE_VERSION;\n}\n","/**\n * Shared purpose mapping utilities for Layer 4 platform adapters.\n *\n * Maps platform-native action names to PDLSS purpose categories.\n * Used by OpenClaw CLI, Cursor, browser, and future adapters.\n */\n\n// -----------------------------------------------------------------------\n// Tool → Purpose mapping\n// -----------------------------------------------------------------------\n\n/** Standard tool name → PDLSS purpose mapping used by all adapters */\nconst TOOL_PURPOSE_MAP: Record<string, string> = {\n // Shell\n shell_exec: 'shell.exec',\n run_command: 'shell.exec',\n execute: 'shell.exec',\n terminal_exec: 'shell.exec',\n run_terminal_command: 'shell.exec',\n\n // File read\n file_read: 'file.read',\n read_file: 'file.read',\n\n // File write\n file_write: 'file.write',\n write_file: 'file.write',\n create_file: 'file.write',\n edit_file: 'file.write',\n\n // File delete\n file_delete: 'file.delete',\n delete_file: 'file.delete',\n\n // Network\n http_request: 'network.request',\n fetch: 'network.request',\n web_request: 'network.request',\n\n // Email\n send_email: 'email.send',\n read_email: 'email.read',\n\n // Calendar\n create_event: 'calendar.create',\n\n // Database\n query_database: 'database.query',\n write_database: 'database.write',\n\n // Payment\n payment_execute: 'payment.execute',\n};\n\n/**\n * Map a tool/action name to a PDLSS purpose category.\n * Returns `tool.<name>` for unmapped tools (denied by default).\n */\nexport function mapToolToPurpose(toolName: string): string {\n return TOOL_PURPOSE_MAP[toolName] || `tool.${toolName}`;\n}\n\n/**\n * Register additional tool → purpose mappings (e.g. from a platform adapter).\n * Does not overwrite existing mappings.\n */\nexport function registerToolMappings(mappings: Record<string, string>): void {\n for (const [tool, purpose] of Object.entries(mappings)) {\n if (!(tool in TOOL_PURPOSE_MAP)) {\n TOOL_PURPOSE_MAP[tool] = purpose;\n }\n }\n}\n\n// -----------------------------------------------------------------------\n// Target extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract the meaningful target string from tool arguments.\n * Uses the purpose category to determine which argument field is relevant.\n */\nexport function extractTarget(toolName: string, args: Record<string, unknown>): string {\n const purpose = mapToolToPurpose(toolName);\n\n if (purpose.startsWith('shell.')) {\n return String(args.command || args.cmd || args.script || '');\n }\n\n if (purpose.startsWith('file.')) {\n return String(args.path || args.file || args.filename || args.file_path || '');\n }\n\n if (purpose.startsWith('network.')) {\n return String(args.url || args.endpoint || args.uri || '');\n }\n\n if (purpose.startsWith('email.')) {\n return String(args.to || args.recipient || args.address || '');\n }\n\n if (purpose.startsWith('database.')) {\n return String(args.query || args.table || '');\n }\n\n if (purpose.startsWith('payment.')) {\n return String(args.description || args.merchant || args.amount || '');\n }\n\n // Fallback: try common field names\n if (args.command) return String(args.command);\n if (args.path) return String(args.path);\n if (args.url) return String(args.url);\n\n // Default: use first non-empty string argument or tool name\n for (const val of Object.values(args)) {\n if (typeof val === 'string' && val.length > 0) return val;\n }\n return toolName;\n}\n\n// -----------------------------------------------------------------------\n// Network domain extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract network domains from a URL target.\n * Returns undefined if the target is not a URL.\n */\nexport function extractNetworkDomains(target: string): string[] | undefined {\n try {\n if (target.startsWith('http://') || target.startsWith('https://')) {\n const url = new URL(target);\n return [url.hostname];\n }\n } catch {\n // Not a URL\n }\n return undefined;\n}\n","/**\n * @astrasyncai/adapter-claude-code — tool → purpose mapping\n *\n * Maps Claude Code's built-in tool names (as delivered in PreToolUse hook\n * payloads) to PDLSS purpose categories, and parses MCP tool names\n * (mcp__{server}__{tool}) into app-connector style purposes.\n */\n\nimport { registerToolMappings } from '../adapter-interface/purpose-mapping';\n\n/** Claude Code built-in tool name → PDLSS purpose */\nexport const CLAUDE_CODE_TOOL_MAP: Record<string, string> = {\n Bash: 'shell.exec',\n Write: 'file.write',\n Edit: 'file.write',\n MultiEdit: 'file.write',\n NotebookEdit: 'file.write',\n Read: 'file.read',\n Glob: 'file.read',\n Grep: 'file.read',\n WebFetch: 'network.request',\n WebSearch: 'network.request',\n Task: 'sub_agent.spawn',\n Agent: 'sub_agent.spawn',\n};\n\nlet registered = false;\n\n/**\n * Register the Claude Code mappings into the shared adapter purpose map.\n * Idempotent; safe to call from both the adapter and the hook entry.\n */\nexport function registerClaudeCodeMappings(): void {\n if (registered) return;\n registerToolMappings(CLAUDE_CODE_TOOL_MAP);\n registered = true;\n}\n\n/**\n * Parse an MCP tool name (mcp__{server}__{tool}) into an app-connector\n * style purpose `{server}.{tool}`. Returns null for non-MCP tool names.\n */\nexport function mapMcpTool(toolName: string): string | null {\n const match = /^mcp__(.+?)__(.+)$/.exec(toolName);\n if (!match) return null;\n return `${match[1]}.${match[2]}`;\n}\n","/**\n * @astrasyncai/adapter-claude-code\n *\n * Layer 4 adapter for Claude Code (Anthropic's CLI coding agent).\n * Intercepts tool calls via Claude Code's native PreToolUse hook:\n * the hook binary receives {tool_name, tool_input, ...} on stdin and\n * answers with a permissionDecision on stdout.\n *\n * Decision mapping is strictly tightening:\n * - DENY → permissionDecision \"deny\"\n * - MANUAL_REVIEW → permissionDecision \"ask\" (Claude Code's own permission\n * prompt is the human-in-the-loop)\n * - ALLOW → no output at all — the guard NEVER emits \"allow\",\n * because \"allow\" would bypass Claude Code's native\n * permission system rather than defer to it.\n *\n * ~250 lines — thin, disposable, platform-specific.\n */\n\nimport type { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface';\nimport type {\n PDLSSContext,\n VerificationDecision,\n AgentAction,\n InterceptResult,\n} from '../gateway/types';\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport { ADAPTER_INTERFACE_VERSION } from '../adapter-interface/interface';\nimport {\n mapToolToPurpose,\n extractTarget,\n extractNetworkDomains,\n} from '../adapter-interface/purpose-mapping';\nimport { registerClaudeCodeMappings, mapMcpTool, CLAUDE_CODE_TOOL_MAP } from './tool-mapping';\n\n// -----------------------------------------------------------------------\n// Claude Code hook payload / output shapes\n// (contract: https://code.claude.com/docs/en/hooks.md)\n// -----------------------------------------------------------------------\n\n/** The PreToolUse payload Claude Code writes to the hook's stdin. */\nexport interface ClaudeCodePreToolUsePayload {\n hook_event_name?: string;\n tool_name: string;\n tool_input: Record<string, unknown>;\n session_id?: string;\n cwd?: string;\n transcript_path?: string;\n permission_mode?: string;\n}\n\n/** The JSON the hook prints to stdout when it wants to tighten a decision. */\nexport interface ClaudeHookOutput {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse';\n permissionDecision: 'deny' | 'ask';\n permissionDecisionReason: string;\n };\n}\n\n// -----------------------------------------------------------------------\n// Configuration\n// -----------------------------------------------------------------------\n\n/**\n * Tools that are never governed: session bookkeeping and UI-only tools\n * with no side effects outside the conversation.\n */\nexport const DEFAULT_SKIP_TOOLS = [\n 'TodoWrite',\n 'TaskCreate',\n 'TaskUpdate',\n 'TaskList',\n 'TaskGet',\n 'ExitPlanMode',\n 'EnterPlanMode',\n 'AskUserQuestion',\n 'BashOutput',\n 'KillShell',\n 'TaskStop',\n 'TaskOutput',\n 'ListMcpResourcesTool',\n];\n\nexport interface ClaudeCodeAdapterOptions {\n /** Tool names to never intercept (replaces DEFAULT_SKIP_TOOLS if given) */\n skipTools?: string[];\n /**\n * When true, unmapped non-MCP tools are still intercepted (purpose\n * `tool.<name>`, denied by default policy). When false (default),\n * unmapped tools are skipped — Claude Code's own permission system\n * remains their only gate.\n */\n strictUnmapped?: boolean;\n /**\n * Govern MCP tools (mcp__{server}__{tool} → purpose {server}.{tool}).\n * Defaults to true. Note the evaluator denies purposes missing from the\n * policy, so with governMcp on, every MCP server in use needs purpose\n * rules; installers may default this off until the policy lists them.\n */\n governMcp?: boolean;\n}\n\n// -----------------------------------------------------------------------\n// Adapter implementation\n// -----------------------------------------------------------------------\n\nexport class ClaudeCodeAdapter implements PlatformAdapter {\n readonly interfaceVersion = ADAPTER_INTERFACE_VERSION;\n\n private gateway!: AstraSyncGateway;\n private skipTools: Set<string>;\n private strictUnmapped: boolean;\n private governMcp: boolean;\n private _isRunning = false;\n\n constructor(options?: ClaudeCodeAdapterOptions) {\n this.skipTools = new Set(options?.skipTools ?? DEFAULT_SKIP_TOOLS);\n this.strictUnmapped = options?.strictUnmapped ?? false;\n this.governMcp = options?.governMcp ?? true;\n registerClaudeCodeMappings();\n }\n\n get isRunning(): boolean {\n return this._isRunning;\n }\n\n async initialize(config: AdapterConfig): Promise<void> {\n this.gateway = config.gateway as AstraSyncGateway;\n\n if (Array.isArray(config.adapterOptions.skipTools)) {\n this.skipTools = new Set(config.adapterOptions.skipTools as string[]);\n }\n if (typeof config.adapterOptions.strictUnmapped === 'boolean') {\n this.strictUnmapped = config.adapterOptions.strictUnmapped;\n }\n\n this._isRunning = true;\n }\n\n async shutdown(): Promise<void> {\n this._isRunning = false;\n }\n\n async interceptAction(action: AgentAction): Promise<InterceptResult> {\n const raw = action.raw as Partial<ClaudeCodePreToolUsePayload> | null;\n\n if (!raw || typeof raw.tool_name !== 'string' || raw.tool_name.length === 0) {\n return { intercepted: false, skipReason: 'No tool_name in payload' };\n }\n\n if (this.skipTools.has(raw.tool_name)) {\n return { intercepted: false, skipReason: `Tool ${raw.tool_name} is not governed` };\n }\n\n const isMcp = mapMcpTool(raw.tool_name) !== null;\n if (isMcp && !this.governMcp) {\n return { intercepted: false, skipReason: `MCP tools not governed (governMcp off)` };\n }\n\n const isMapped = raw.tool_name in CLAUDE_CODE_TOOL_MAP || isMcp;\n if (!isMapped && !this.strictUnmapped) {\n return {\n intercepted: false,\n skipReason: `Unmapped tool ${raw.tool_name} (strictUnmapped off)`,\n };\n }\n\n const context = this.extractContext(action);\n return { intercepted: true, context };\n }\n\n extractContext(action: AgentAction): PDLSSContext {\n const raw = action.raw as ClaudeCodePreToolUsePayload;\n const toolName = raw.tool_name;\n const args = (raw.tool_input ?? {}) as Record<string, unknown>;\n\n const mcpPurpose = mapMcpTool(toolName);\n const purpose = mcpPurpose ?? mapToolToPurpose(toolName);\n const target = extractTarget(toolName, args);\n const networkAccess = extractNetworkDomains(target);\n\n return {\n purpose,\n action: toolName,\n target,\n ...(networkAccess && { networkAccess }),\n metadata: {\n ...(raw.session_id && { sessionId: raw.session_id }),\n ...(raw.cwd && { cwd: raw.cwd }),\n platform: 'claude-code',\n },\n };\n }\n\n /**\n * Translate a gateway decision into hook stdout JSON.\n * Returns null on ALLOW — the hook stays silent and Claude Code's own\n * permission system remains in charge (strictly-tightening invariant).\n */\n decisionToHookOutput(decision: VerificationDecision): ClaudeHookOutput | null {\n switch (decision.recommendation) {\n case 'DENY':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: `AstraSync Local Guard: ${decision.reason}`,\n },\n };\n case 'MANUAL_REVIEW':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'ask',\n permissionDecisionReason: `AstraSync Local Guard requires approval: ${decision.reason}`,\n },\n };\n case 'ALLOW':\n return null;\n }\n }\n\n /**\n * Convenience for the hook entry: full payload → hook output (or null).\n */\n async evaluatePayload(payload: ClaudeCodePreToolUsePayload): Promise<ClaudeHookOutput | null> {\n const action: AgentAction = {\n raw: payload,\n platform: 'claude-code',\n timestamp: new Date(),\n };\n\n const interception = await this.interceptAction(action);\n if (!interception.intercepted || !interception.context) return null;\n\n const decision = await this.gateway.evaluate(interception.context);\n return this.decisionToHookOutput(decision);\n }\n\n /**\n * Enforcement happens via the stdout JSON the hook prints (see\n * decisionToHookOutput); there is no in-process side to enforce on.\n */\n async enforceDecision(_decision: VerificationDecision): Promise<void> {\n // Contract no-op: Claude Code enforces based on the hook's stdout.\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/adapter-interface/interface.ts","../../src/adapter-interface/purpose-mapping.ts","../../src/claude-code/tool-mapping.ts","../../src/claude-code/claude-code-adapter.ts"],"sourcesContent":["/**\n * PlatformAdapter Interface\n *\n * The contract that Layer 4 platform adapters implement.\n * Agent-side interception: governs what agents are allowed to do before they do it.\n *\n * Each adapter is 200-500 lines of platform-specific code.\n * All verification logic lives in the gateway — adapters just translate\n * between the platform's world and AstraSync's world.\n */\n\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport type { PDLSSContext, VerificationDecision, AgentAction, InterceptResult } from '../gateway/types';\n\nexport interface AdapterConfig {\n /** The AstraSyncGateway instance (handles mode routing) */\n gateway: AstraSyncGateway;\n /** Platform-specific configuration */\n adapterOptions: Record<string, unknown>;\n}\n\nexport interface PlatformAdapter {\n /**\n * Interface version for compatibility checking.\n * Current version: 1.\n */\n readonly interfaceVersion: number;\n\n /**\n * Platform-specific initialization.\n * Load config, register hooks, establish connections.\n */\n initialize(config: AdapterConfig): Promise<void>;\n\n /**\n * Graceful shutdown.\n * Drain in-flight verifications, deregister hooks, close connections.\n */\n shutdown(): Promise<void>;\n\n /**\n * Intercept an agent action before execution.\n *\n * How this works depends on the platform:\n * - CLI adapter: proxy server captures outbound request\n * - Browser adapter: content script intercepts DOM interaction\n * - Express: middleware captures inbound request\n */\n interceptAction(action: AgentAction): Promise<InterceptResult>;\n\n /**\n * Extract PDLSS-compatible context from a platform-specific action.\n * Maps platform-native action format to the universal PDLSSContext.\n */\n extractContext(action: AgentAction): PDLSSContext;\n\n /**\n * Enforce the verification decision in a platform-specific way.\n *\n * How this works depends on the platform:\n * - CLI: block command / allow command / prompt for approval\n * - Browser: block navigation / show confirmation dialog\n * - Express: return 403 / pass through / inject headers\n */\n enforceDecision(decision: VerificationDecision): Promise<void>;\n}\n\n/**\n * Current adapter interface version.\n */\nexport const ADAPTER_INTERFACE_VERSION = 1;\n\n/**\n * Check if an adapter is compatible with the current interface version.\n */\nexport function isCompatibleAdapter(adapter: PlatformAdapter): boolean {\n return adapter.interfaceVersion === ADAPTER_INTERFACE_VERSION;\n}\n","/**\n * Shared purpose mapping utilities for Layer 4 platform adapters.\n *\n * Maps platform-native action names to PDLSS purpose categories.\n * Used by OpenClaw CLI, Cursor, browser, and future adapters.\n */\n\n// -----------------------------------------------------------------------\n// Tool → Purpose mapping\n// -----------------------------------------------------------------------\n\n/** Standard tool name → PDLSS purpose mapping used by all adapters */\nconst TOOL_PURPOSE_MAP: Record<string, string> = {\n // Shell\n shell_exec: 'shell.exec',\n run_command: 'shell.exec',\n execute: 'shell.exec',\n terminal_exec: 'shell.exec',\n run_terminal_command: 'shell.exec',\n\n // File read\n file_read: 'file.read',\n read_file: 'file.read',\n\n // File write\n file_write: 'file.write',\n write_file: 'file.write',\n create_file: 'file.write',\n edit_file: 'file.write',\n\n // File delete\n file_delete: 'file.delete',\n delete_file: 'file.delete',\n\n // Network\n http_request: 'network.request',\n fetch: 'network.request',\n web_request: 'network.request',\n\n // Email\n send_email: 'email.send',\n read_email: 'email.read',\n\n // Calendar\n create_event: 'calendar.create',\n\n // Database\n query_database: 'database.query',\n write_database: 'database.write',\n\n // Payment\n payment_execute: 'payment.execute',\n};\n\n/**\n * Map a tool/action name to a PDLSS purpose category.\n * Returns `tool.<name>` for unmapped tools (denied by default).\n */\nexport function mapToolToPurpose(toolName: string): string {\n return TOOL_PURPOSE_MAP[toolName] || `tool.${toolName}`;\n}\n\n/**\n * Register additional tool → purpose mappings (e.g. from a platform adapter).\n * Does not overwrite existing mappings.\n */\nexport function registerToolMappings(mappings: Record<string, string>): void {\n for (const [tool, purpose] of Object.entries(mappings)) {\n if (!(tool in TOOL_PURPOSE_MAP)) {\n TOOL_PURPOSE_MAP[tool] = purpose;\n }\n }\n}\n\n// -----------------------------------------------------------------------\n// Target extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract the meaningful target string from tool arguments.\n * Uses the purpose category to determine which argument field is relevant.\n */\nexport function extractTarget(toolName: string, args: Record<string, unknown>): string {\n const purpose = mapToolToPurpose(toolName);\n\n if (purpose.startsWith('shell.')) {\n return String(args.command || args.cmd || args.script || '');\n }\n\n if (purpose.startsWith('file.')) {\n return String(args.path || args.file || args.filename || args.file_path || '');\n }\n\n if (purpose.startsWith('network.')) {\n return String(args.url || args.endpoint || args.uri || '');\n }\n\n if (purpose.startsWith('email.')) {\n return String(args.to || args.recipient || args.address || '');\n }\n\n if (purpose.startsWith('database.')) {\n return String(args.query || args.table || '');\n }\n\n if (purpose.startsWith('payment.')) {\n return String(args.description || args.merchant || args.amount || '');\n }\n\n // Fallback: try common field names\n if (args.command) return String(args.command);\n if (args.path) return String(args.path);\n if (args.url) return String(args.url);\n\n // Default: use first non-empty string argument or tool name\n for (const val of Object.values(args)) {\n if (typeof val === 'string' && val.length > 0) return val;\n }\n return toolName;\n}\n\n// -----------------------------------------------------------------------\n// Network domain extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract network domains from a URL target.\n * Returns undefined if the target is not a URL.\n */\nexport function extractNetworkDomains(target: string): string[] | undefined {\n try {\n if (target.startsWith('http://') || target.startsWith('https://')) {\n const url = new URL(target);\n return [url.hostname];\n }\n } catch {\n // Not a URL\n }\n return undefined;\n}\n","/**\n * @astrasyncai/adapter-claude-code — tool → purpose mapping\n *\n * Maps Claude Code's built-in tool names (as delivered in PreToolUse hook\n * payloads) to PDLSS purpose categories, and parses MCP tool names\n * (mcp__{server}__{tool}) into app-connector style purposes.\n */\n\nimport { registerToolMappings } from '../adapter-interface/purpose-mapping';\n\n/** Claude Code built-in tool name → PDLSS purpose */\nexport const CLAUDE_CODE_TOOL_MAP: Record<string, string> = {\n Bash: 'shell.exec',\n // Native Windows ships shell execution as a distinct PowerShell tool\n // (hooks docs: \"Tool Names in PreToolUse\"). Found live on 10 Jul 2026 —\n // the desktop-app runtime verification showed shell commands bypassing\n // the guard entirely on Windows because only Bash was mapped/matched.\n PowerShell: 'shell.exec',\n Write: 'file.write',\n Edit: 'file.write',\n MultiEdit: 'file.write',\n NotebookEdit: 'file.write',\n Read: 'file.read',\n Glob: 'file.read',\n Grep: 'file.read',\n WebFetch: 'network.request',\n WebSearch: 'network.request',\n Task: 'sub_agent.spawn',\n Agent: 'sub_agent.spawn',\n};\n\nlet registered = false;\n\n/**\n * Register the Claude Code mappings into the shared adapter purpose map.\n * Idempotent; safe to call from both the adapter and the hook entry.\n */\nexport function registerClaudeCodeMappings(): void {\n if (registered) return;\n registerToolMappings(CLAUDE_CODE_TOOL_MAP);\n registered = true;\n}\n\n/**\n * Parse an MCP tool name (mcp__{server}__{tool}) into an app-connector\n * style purpose `{server}.{tool}`. Returns null for non-MCP tool names.\n */\nexport function mapMcpTool(toolName: string): string | null {\n const match = /^mcp__(.+?)__(.+)$/.exec(toolName);\n if (!match) return null;\n return `${match[1]}.${match[2]}`;\n}\n","/**\n * @astrasyncai/adapter-claude-code\n *\n * Layer 4 adapter for Claude Code (Anthropic's CLI coding agent).\n * Intercepts tool calls via Claude Code's native PreToolUse hook:\n * the hook binary receives {tool_name, tool_input, ...} on stdin and\n * answers with a permissionDecision on stdout.\n *\n * Decision mapping is strictly tightening:\n * - DENY → permissionDecision \"deny\"\n * - MANUAL_REVIEW → permissionDecision \"ask\" (Claude Code's own permission\n * prompt is the human-in-the-loop)\n * - ALLOW → no output at all — the guard NEVER emits \"allow\",\n * because \"allow\" would bypass Claude Code's native\n * permission system rather than defer to it.\n *\n * ~250 lines — thin, disposable, platform-specific.\n */\n\nimport type { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface';\nimport type {\n PDLSSContext,\n VerificationDecision,\n AgentAction,\n InterceptResult,\n} from '../gateway/types';\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport { ADAPTER_INTERFACE_VERSION } from '../adapter-interface/interface';\nimport {\n mapToolToPurpose,\n extractTarget,\n extractNetworkDomains,\n} from '../adapter-interface/purpose-mapping';\nimport { registerClaudeCodeMappings, mapMcpTool, CLAUDE_CODE_TOOL_MAP } from './tool-mapping';\n\n// -----------------------------------------------------------------------\n// Claude Code hook payload / output shapes\n// (contract: https://code.claude.com/docs/en/hooks.md)\n// -----------------------------------------------------------------------\n\n/** The PreToolUse payload Claude Code writes to the hook's stdin. */\nexport interface ClaudeCodePreToolUsePayload {\n hook_event_name?: string;\n tool_name: string;\n tool_input: Record<string, unknown>;\n session_id?: string;\n cwd?: string;\n transcript_path?: string;\n permission_mode?: string;\n}\n\n/** The JSON the hook prints to stdout when it wants to tighten a decision. */\nexport interface ClaudeHookOutput {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse';\n permissionDecision: 'deny' | 'ask';\n permissionDecisionReason: string;\n };\n}\n\n// -----------------------------------------------------------------------\n// Configuration\n// -----------------------------------------------------------------------\n\n/**\n * Tools that are never governed: session bookkeeping and UI-only tools\n * with no side effects outside the conversation.\n */\nexport const DEFAULT_SKIP_TOOLS = [\n 'TodoWrite',\n 'TaskCreate',\n 'TaskUpdate',\n 'TaskList',\n 'TaskGet',\n 'ExitPlanMode',\n 'EnterPlanMode',\n 'AskUserQuestion',\n 'BashOutput',\n 'KillShell',\n 'TaskStop',\n 'TaskOutput',\n 'ListMcpResourcesTool',\n];\n\nexport interface ClaudeCodeAdapterOptions {\n /** Tool names to never intercept (replaces DEFAULT_SKIP_TOOLS if given) */\n skipTools?: string[];\n /**\n * When true, unmapped non-MCP tools are still intercepted (purpose\n * `tool.<name>`, denied by default policy). When false (default),\n * unmapped tools are skipped — Claude Code's own permission system\n * remains their only gate.\n */\n strictUnmapped?: boolean;\n /**\n * Govern MCP tools (mcp__{server}__{tool} → purpose {server}.{tool}).\n * Defaults to true. Note the evaluator denies purposes missing from the\n * policy, so with governMcp on, every MCP server in use needs purpose\n * rules; installers may default this off until the policy lists them.\n */\n governMcp?: boolean;\n}\n\n// -----------------------------------------------------------------------\n// Adapter implementation\n// -----------------------------------------------------------------------\n\nexport class ClaudeCodeAdapter implements PlatformAdapter {\n readonly interfaceVersion = ADAPTER_INTERFACE_VERSION;\n\n private gateway!: AstraSyncGateway;\n private skipTools: Set<string>;\n private strictUnmapped: boolean;\n private governMcp: boolean;\n private _isRunning = false;\n\n constructor(options?: ClaudeCodeAdapterOptions) {\n this.skipTools = new Set(options?.skipTools ?? DEFAULT_SKIP_TOOLS);\n this.strictUnmapped = options?.strictUnmapped ?? false;\n this.governMcp = options?.governMcp ?? true;\n registerClaudeCodeMappings();\n }\n\n get isRunning(): boolean {\n return this._isRunning;\n }\n\n async initialize(config: AdapterConfig): Promise<void> {\n this.gateway = config.gateway as AstraSyncGateway;\n\n if (Array.isArray(config.adapterOptions.skipTools)) {\n this.skipTools = new Set(config.adapterOptions.skipTools as string[]);\n }\n if (typeof config.adapterOptions.strictUnmapped === 'boolean') {\n this.strictUnmapped = config.adapterOptions.strictUnmapped;\n }\n\n this._isRunning = true;\n }\n\n async shutdown(): Promise<void> {\n this._isRunning = false;\n }\n\n async interceptAction(action: AgentAction): Promise<InterceptResult> {\n const raw = action.raw as Partial<ClaudeCodePreToolUsePayload> | null;\n\n if (!raw || typeof raw.tool_name !== 'string' || raw.tool_name.length === 0) {\n return { intercepted: false, skipReason: 'No tool_name in payload' };\n }\n\n if (this.skipTools.has(raw.tool_name)) {\n return { intercepted: false, skipReason: `Tool ${raw.tool_name} is not governed` };\n }\n\n const isMcp = mapMcpTool(raw.tool_name) !== null;\n if (isMcp && !this.governMcp) {\n return { intercepted: false, skipReason: `MCP tools not governed (governMcp off)` };\n }\n\n const isMapped = raw.tool_name in CLAUDE_CODE_TOOL_MAP || isMcp;\n if (!isMapped && !this.strictUnmapped) {\n return {\n intercepted: false,\n skipReason: `Unmapped tool ${raw.tool_name} (strictUnmapped off)`,\n };\n }\n\n const context = this.extractContext(action);\n return { intercepted: true, context };\n }\n\n extractContext(action: AgentAction): PDLSSContext {\n const raw = action.raw as ClaudeCodePreToolUsePayload;\n const toolName = raw.tool_name;\n const args = (raw.tool_input ?? {}) as Record<string, unknown>;\n\n const mcpPurpose = mapMcpTool(toolName);\n const purpose = mcpPurpose ?? mapToolToPurpose(toolName);\n const target = extractTarget(toolName, args);\n const networkAccess = extractNetworkDomains(target);\n\n return {\n purpose,\n action: toolName,\n target,\n ...(networkAccess && { networkAccess }),\n metadata: {\n ...(raw.session_id && { sessionId: raw.session_id }),\n ...(raw.cwd && { cwd: raw.cwd }),\n platform: 'claude-code',\n },\n };\n }\n\n /**\n * Translate a gateway decision into hook stdout JSON.\n * Returns null on ALLOW — the hook stays silent and Claude Code's own\n * permission system remains in charge (strictly-tightening invariant).\n */\n decisionToHookOutput(decision: VerificationDecision): ClaudeHookOutput | null {\n switch (decision.recommendation) {\n case 'DENY':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: `AstraSync Local Guard: ${decision.reason}`,\n },\n };\n case 'MANUAL_REVIEW':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'ask',\n permissionDecisionReason: `AstraSync Local Guard requires approval: ${decision.reason}`,\n },\n };\n case 'ALLOW':\n return null;\n }\n }\n\n /**\n * Convenience for the hook entry: full payload → hook output (or null).\n */\n async evaluatePayload(payload: ClaudeCodePreToolUsePayload): Promise<ClaudeHookOutput | null> {\n const action: AgentAction = {\n raw: payload,\n platform: 'claude-code',\n timestamp: new Date(),\n };\n\n const interception = await this.interceptAction(action);\n if (!interception.intercepted || !interception.context) {\n // Visible skip: an ungoverned tool must be distinguishable from an\n // ALLOW in the traces (a silent skip hid the Windows PowerShell\n // coverage gap for a full passive week).\n this.gateway.logSkippedTool(payload.tool_name, interception.skipReason ?? 'not intercepted');\n return null;\n }\n\n const decision = await this.gateway.evaluate(interception.context);\n return this.decisionToHookOutput(decision);\n }\n\n /**\n * Enforcement happens via the stdout JSON the hook prints (see\n * decisionToHookOutput); there is no in-process side to enforce on.\n */\n async enforceDecision(_decision: VerificationDecision): Promise<void> {\n // Contract no-op: Claude Code enforces based on the hook's stdout.\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,230 @@
1
+ import { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface.mjs';
2
+ import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-BV6abBnF.mjs';
3
+ import { AstraSyncGateway } from '../gateway/gateway.mjs';
4
+ import '../types-sy3tq8lp.mjs';
5
+ import '../metadata-capture.mjs';
6
+
7
+ /**
8
+ * @astrasyncai/adapter-codex
9
+ *
10
+ * Layer 4 adapter for OpenAI Codex CLI. Intercepts tool calls via Codex's
11
+ * native PreToolUse hooks (stable since Codex v0.124.0): the hook binary
12
+ * receives {tool_name, tool_input, permission_mode, ...} on stdin and may
13
+ * answer with a permissionDecision on stdout.
14
+ *
15
+ * Decision mapping is strictly tightening, with two Codex-specific twists
16
+ * versus the Claude Code adapter (§7.7):
17
+ *
18
+ * - Codex has NO "ask" decision. MANUAL_REVIEW therefore emits nothing —
19
+ * "no matching hook decides" makes Codex fall through to its own native
20
+ * approval prompt, which is the HITL. EXCEPT when the session runs in
21
+ * `dontAsk` / `bypassPermissions` permission mode: no native prompt will
22
+ * catch the fall-through there, so MANUAL_REVIEW escalates to deny.
23
+ * - Codex FAILS OPEN on hook errors/timeouts (it "reports the error and
24
+ * continues the tool call"). The hook binary therefore fails closed
25
+ * internally — every internal failure becomes an explicit deny (unless
26
+ * failOpen is configured). A hard kill/timeout still fails open on
27
+ * Codex's side; that residual gap is documented, and the mitigation is
28
+ * the hook's ~200ms cost against a 10s timeout.
29
+ *
30
+ * ALLOW never emits "allow" (and never `updatedInput`) — a hook allow would
31
+ * suppress Codex's own approval prompt rather than defer to it.
32
+ */
33
+
34
+ /** The PreToolUse payload Codex CLI writes to the hook's stdin. */
35
+ interface CodexPreToolUsePayload {
36
+ hook_event_name?: string;
37
+ tool_name: string;
38
+ tool_input: Record<string, unknown>;
39
+ session_id?: string;
40
+ turn_id?: string;
41
+ tool_use_id?: string;
42
+ cwd?: string;
43
+ model?: string;
44
+ transcript_path?: string | null;
45
+ /** default | acceptEdits | plan | dontAsk | bypassPermissions */
46
+ permission_mode?: string;
47
+ }
48
+ /**
49
+ * The JSON the hook prints to stdout when it wants to tighten a decision.
50
+ * Deny-only by design: Codex has no "ask", and the guard never emits
51
+ * "allow". Any unsupported field would make Codex mark the hook run failed
52
+ * and continue the call — keep this shape strictly to documented fields.
53
+ */
54
+ interface CodexHookOutput {
55
+ hookSpecificOutput: {
56
+ hookEventName: 'PreToolUse';
57
+ permissionDecision: 'deny';
58
+ permissionDecisionReason: string;
59
+ };
60
+ }
61
+ /** Permission modes with no native approval prompt to catch a fall-through. */
62
+ declare const UNPROMPTED_PERMISSION_MODES: Set<string>;
63
+ /** Session bookkeeping tools that are never governed. */
64
+ declare const DEFAULT_SKIP_TOOLS: string[];
65
+ interface CodexAdapterOptions {
66
+ /** Tool names to never intercept (replaces DEFAULT_SKIP_TOOLS if given) */
67
+ skipTools?: string[];
68
+ /**
69
+ * When true, unmapped non-MCP tools are still intercepted (purpose
70
+ * `tool.<name>`, denied by default policy). When false (default),
71
+ * unmapped tools are skipped — Codex's own approval flow remains their
72
+ * only gate.
73
+ */
74
+ strictUnmapped?: boolean;
75
+ /**
76
+ * Govern MCP tools (mcp__{server}__{tool} → purpose {server}.{tool}).
77
+ * Defaults to true; installers default it off until the policy lists
78
+ * {server}.{tool} rules (the evaluator denies unknown purposes).
79
+ */
80
+ governMcp?: boolean;
81
+ }
82
+ declare class CodexAdapter implements PlatformAdapter {
83
+ readonly interfaceVersion = 1;
84
+ private gateway;
85
+ private skipTools;
86
+ private strictUnmapped;
87
+ private governMcp;
88
+ private _isRunning;
89
+ constructor(options?: CodexAdapterOptions);
90
+ get isRunning(): boolean;
91
+ initialize(config: AdapterConfig): Promise<void>;
92
+ shutdown(): Promise<void>;
93
+ interceptAction(action: AgentAction): Promise<InterceptResult>;
94
+ extractContext(action: AgentAction): PDLSSContext;
95
+ private buildContext;
96
+ /**
97
+ * Translate a gateway decision into hook stdout JSON.
98
+ *
99
+ * Returns null on ALLOW (silent — Codex's own approval flow stays in
100
+ * charge) and on MANUAL_REVIEW in prompted permission modes (the
101
+ * fall-through IS Codex's native prompt). MANUAL_REVIEW in dontAsk /
102
+ * bypassPermissions escalates to deny — no prompt exists to catch it.
103
+ */
104
+ decisionToHookOutput(decision: VerificationDecision, permissionMode?: string): CodexHookOutput | null;
105
+ /**
106
+ * Convenience for the hook entry: full payload → hook output (or null).
107
+ *
108
+ * apply_patch envelopes are evaluated per touched file (one context per
109
+ * `*** Add/Update/Delete File:` header) and tightened to the worst
110
+ * decision, so file.write blockedPatterns (including guard
111
+ * self-protection) apply to every path in the patch.
112
+ */
113
+ evaluatePayload(payload: CodexPreToolUsePayload): Promise<CodexHookOutput | null>;
114
+ private evaluateInterceptedPayload;
115
+ /**
116
+ * Enforcement happens via the stdout JSON the hook prints; there is no
117
+ * in-process side to enforce on.
118
+ */
119
+ enforceDecision(_decision: VerificationDecision): Promise<void>;
120
+ }
121
+
122
+ /**
123
+ * Parse an MCP tool name (mcp__{server}__{tool}) into an app-connector
124
+ * style purpose `{server}.{tool}`. Returns null for non-MCP tool names.
125
+ */
126
+ declare function mapMcpTool(toolName: string): string | null;
127
+
128
+ /**
129
+ * @astrasyncai/adapter-codex — tool → purpose mapping
130
+ *
131
+ * Maps OpenAI Codex CLI's built-in tool names (as delivered in PreToolUse
132
+ * hook payloads) to PDLSS purpose categories. MCP tool names use the same
133
+ * mcp__{server}__{tool} convention as Claude Code, so the parser is shared.
134
+ */
135
+
136
+ /**
137
+ * Codex CLI built-in tool name → PDLSS purpose.
138
+ * Documented canonical names: `Bash`, `apply_patch` (aliases `Edit`/`Write`
139
+ * appear in matcher examples), `web_search`. Undocumented tools stay
140
+ * unmapped and are skipped unless strictUnmapped is on.
141
+ */
142
+ declare const CODEX_TOOL_MAP: Record<string, string>;
143
+ /**
144
+ * Register the Codex mappings into the shared adapter purpose map.
145
+ * Idempotent; safe to call from both the adapter and the hook entry.
146
+ */
147
+ declare function registerCodexMappings(): void;
148
+ /**
149
+ * Extract the file paths an apply_patch envelope touches. Codex's patch
150
+ * format marks each file with `*** Add File:` / `*** Update File:` /
151
+ * `*** Delete File:` headers. Returns [] when nothing parseable is found —
152
+ * callers should then fall back to a raw-text target.
153
+ */
154
+ declare function extractApplyPatchPaths(patchText: string): string[];
155
+
156
+ /**
157
+ * AstraSync Local Guard for OpenAI Codex CLI — PreToolUse hook entry.
158
+ *
159
+ * Codex invokes this binary for each governed tool call, writing the
160
+ * PreToolUse payload to stdin. The hook evaluates the call against the
161
+ * local PDLSS policy (shared with the Claude Code adapter: same
162
+ * ~/.astrasync/policy.yaml + guard.json) and prints a permissionDecision
163
+ * JSON to stdout only when it wants to tighten the outcome (deny). On
164
+ * ALLOW — and on MANUAL_REVIEW in prompted permission modes — it prints
165
+ * nothing, so Codex's own approval flow stays in charge.
166
+ *
167
+ * Fail posture: Codex itself FAILS OPEN on hook errors ("reports the error
168
+ * and continues the tool call"), so unlike the Claude Code hook we cannot
169
+ * lean on exit codes alone — every internal failure is converted into an
170
+ * explicit deny decision (opt-outs: ASTRASYNC_GUARD_FAIL_OPEN=1 or
171
+ * guard.json {"failOpen": true}). A missing policy file everywhere means
172
+ * the guard simply isn't configured — silent no-op, never a block.
173
+ */
174
+
175
+ interface ProcessCodexHookOptions {
176
+ env?: NodeJS.ProcessEnv;
177
+ home?: string;
178
+ /** Injectable for tests */
179
+ gatewayFactory?: (config: ConstructorParameters<typeof AstraSyncGateway>[0]) => AstraSyncGateway;
180
+ }
181
+ interface ProcessCodexHookResult {
182
+ output: CodexHookOutput | null;
183
+ exitCode: number;
184
+ }
185
+ declare function processCodexHookPayload(payloadJson: string, options?: ProcessCodexHookOptions): Promise<ProcessCodexHookResult>;
186
+
187
+ /**
188
+ * AstraSync Local Guard for Claude Code — settings.json installer.
189
+ *
190
+ * Merges the guard's PreToolUse hook entry into a Claude Code settings
191
+ * file (~/.claude/settings.json or <project>/.claude/settings.json)
192
+ * without clobbering anything else in it, and builds the default policy
193
+ * for a coding agent.
194
+ */
195
+
196
+ interface InstallResult {
197
+ installed: boolean;
198
+ alreadyPresent: boolean;
199
+ updated: boolean;
200
+ }
201
+
202
+ /**
203
+ * AstraSync Local Guard for Codex CLI — hooks.json installer.
204
+ *
205
+ * Codex reads hooks from ~/.codex/hooks.json (user scope) or
206
+ * <repo>/.codex/hooks.json (project scope) with the SAME JSON shape as
207
+ * Claude Code's settings.json (`hooks.PreToolUse[].hooks[]`), so the merge
208
+ * and uninstall machinery is shared with the Claude Code installer — only
209
+ * the marker, matcher, and file location differ.
210
+ *
211
+ * The policy itself is shared with every other Local Guard adapter on the
212
+ * machine: ~/.astrasync/policy.yaml + guard.json. One policy per user.
213
+ */
214
+
215
+ /** Substring that identifies the guard's hook entry inside hooks.json */
216
+ declare const ASTRASYNC_CODEX_HOOK_MARKER = "astrasync-codex-hook";
217
+ /**
218
+ * Tools worth spawning the hook for. Codex matchers are regexes over
219
+ * tool_name; bookkeeping tools (update_plan) never pay node startup cost.
220
+ */
221
+ declare const CODEX_HOOK_MATCHER = "Bash|apply_patch|Edit|Write|web_search|mcp__.*";
222
+ /**
223
+ * Merge the guard's PreToolUse entry into a Codex hooks.json file.
224
+ * Same never-clobber/idempotent semantics as the Claude Code installer.
225
+ */
226
+ declare function installCodexHooks(hooksPath: string, hookCommand: string): InstallResult;
227
+ /** Remove the guard's PreToolUse entry from a Codex hooks.json file. */
228
+ declare function uninstallCodexHooks(hooksPath: string): boolean;
229
+
230
+ export { ASTRASYNC_CODEX_HOOK_MARKER, CODEX_HOOK_MATCHER, CODEX_TOOL_MAP, CodexAdapter, type CodexAdapterOptions, type CodexHookOutput, type CodexPreToolUsePayload, DEFAULT_SKIP_TOOLS, type ProcessCodexHookOptions, type ProcessCodexHookResult, UNPROMPTED_PERMISSION_MODES, extractApplyPatchPaths, installCodexHooks, mapMcpTool, processCodexHookPayload, registerCodexMappings, uninstallCodexHooks };