@zhin.js/agent 1.1.39 → 1.1.40

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 (114) hide show
  1. package/lib/assistant/job-storage.d.ts +238 -0
  2. package/lib/assistant/job-storage.js +143 -0
  3. package/lib/assistant/job-storage.js.map +1 -0
  4. package/lib/assistant/job-store.d.ts +7 -0
  5. package/lib/assistant/job-store.js +41 -3
  6. package/lib/assistant/job-store.js.map +1 -1
  7. package/lib/config/perception-config.d.ts +9 -0
  8. package/lib/config/perception-config.js +27 -0
  9. package/lib/config/perception-config.js.map +1 -1
  10. package/lib/context/context-system.d.ts +1 -0
  11. package/lib/context/context-system.js +8 -8
  12. package/lib/context/context-system.js.map +1 -1
  13. package/lib/context/im-turn-context-adapter.js +2 -0
  14. package/lib/context/im-turn-context-adapter.js.map +1 -1
  15. package/lib/context/skill-context.d.ts +3 -0
  16. package/lib/context/skill-context.js +6 -0
  17. package/lib/context/skill-context.js.map +1 -0
  18. package/lib/context/turn-envelope.d.ts +4 -0
  19. package/lib/context/turn-envelope.js +24 -7
  20. package/lib/context/turn-envelope.js.map +1 -1
  21. package/lib/context/turn-user-message.d.ts +3 -1
  22. package/lib/context/turn-user-message.js +14 -1
  23. package/lib/context/turn-user-message.js.map +1 -1
  24. package/lib/core/agent-core-run.d.ts +1 -0
  25. package/lib/core/agent-core-run.js +47 -20
  26. package/lib/core/agent-core-run.js.map +1 -1
  27. package/lib/core/structured-reply-recovery.d.ts +6 -2
  28. package/lib/core/structured-reply-recovery.js +22 -8
  29. package/lib/core/structured-reply-recovery.js.map +1 -1
  30. package/lib/core/turn-completion-usage.d.ts +2 -1
  31. package/lib/core/turn-completion-usage.js +14 -5
  32. package/lib/core/turn-completion-usage.js.map +1 -1
  33. package/lib/core/turn-event-mapper.d.ts +1 -1
  34. package/lib/core/turn-event-mapper.js +8 -5
  35. package/lib/core/turn-event-mapper.js.map +1 -1
  36. package/lib/event/turn-event.d.ts +7 -1
  37. package/lib/event/turn-event.js.map +1 -1
  38. package/lib/index.d.ts +4 -1
  39. package/lib/index.js +2 -0
  40. package/lib/index.js.map +1 -1
  41. package/lib/media/perception.d.ts +3 -4
  42. package/lib/media/perception.js +59 -37
  43. package/lib/media/perception.js.map +1 -1
  44. package/lib/memory/compaction-runtime.d.ts +3 -2
  45. package/lib/memory/compaction-runtime.js +22 -14
  46. package/lib/memory/compaction-runtime.js.map +1 -1
  47. package/lib/outbound/agent-output.js +2 -0
  48. package/lib/outbound/agent-output.js.map +1 -1
  49. package/lib/outbound/chat-failure.d.ts +27 -0
  50. package/lib/outbound/chat-failure.js +53 -0
  51. package/lib/outbound/chat-failure.js.map +1 -0
  52. package/lib/outbound/structured-reply.d.ts +7 -0
  53. package/lib/outbound/structured-reply.js +52 -16
  54. package/lib/outbound/structured-reply.js.map +1 -1
  55. package/lib/plugin-runtime/agent-runtime.js +49 -8
  56. package/lib/plugin-runtime/agent-runtime.js.map +1 -1
  57. package/lib/plugin-runtime/ai-completion.d.ts +4 -3
  58. package/lib/plugin-runtime/ai-completion.js +64 -10
  59. package/lib/plugin-runtime/ai-completion.js.map +1 -1
  60. package/lib/plugin-runtime/capability-ingress.js +3 -1
  61. package/lib/plugin-runtime/capability-ingress.js.map +1 -1
  62. package/lib/plugin-runtime/deferred-capability-plan.d.ts +4 -1
  63. package/lib/plugin-runtime/deferred-capability-plan.js +25 -9
  64. package/lib/plugin-runtime/deferred-capability-plan.js.map +1 -1
  65. package/lib/plugin-runtime/full-agent-turn-engine.js +1 -0
  66. package/lib/plugin-runtime/full-agent-turn-engine.js.map +1 -1
  67. package/lib/plugin-runtime/index.d.ts +2 -0
  68. package/lib/plugin-runtime/index.js +2 -0
  69. package/lib/plugin-runtime/index.js.map +1 -1
  70. package/lib/prompt/assembly.js +0 -9
  71. package/lib/prompt/assembly.js.map +1 -1
  72. package/lib/prompt/git-context.js +1 -1
  73. package/lib/prompt/git-context.js.map +1 -1
  74. package/lib/prompt/rules/chat-rules.js +8 -9
  75. package/lib/prompt/rules/chat-rules.js.map +1 -1
  76. package/lib/prompt/rules/critical-rules.js +10 -10
  77. package/lib/prompt/rules/critical-rules.js.map +1 -1
  78. package/lib/prompt/system-prompt.js +4 -5
  79. package/lib/prompt/system-prompt.js.map +1 -1
  80. package/lib/security/file-role-policy.js +6 -6
  81. package/lib/security/file-role-policy.js.map +1 -1
  82. package/lib/security/policy-facade.js +1 -1
  83. package/lib/security/policy-facade.js.map +1 -1
  84. package/lib/security/usage-quota.d.ts +4 -0
  85. package/lib/security/usage-quota.js +23 -2
  86. package/lib/security/usage-quota.js.map +1 -1
  87. package/lib/session/approval-port.d.ts +2 -0
  88. package/lib/session/approval-port.js.map +1 -1
  89. package/lib/session/approval-review-agent.d.ts +1 -1
  90. package/lib/session/approval-review-agent.js +19 -4
  91. package/lib/session/approval-review-agent.js.map +1 -1
  92. package/lib/session/turn-ingress-session.js +7 -1
  93. package/lib/session/turn-ingress-session.js.map +1 -1
  94. package/lib/tool/tool-approval-gate.js +4 -2
  95. package/lib/tool/tool-approval-gate.js.map +1 -1
  96. package/lib/tool/turn-tool-runtime.js +24 -5
  97. package/lib/tool/turn-tool-runtime.js.map +1 -1
  98. package/lib/tool-catalog/deferred-turn-controller.js +1 -4
  99. package/lib/tool-catalog/deferred-turn-controller.js.map +1 -1
  100. package/lib/trace/context-budget.d.ts +4 -3
  101. package/lib/trace/context-budget.js +28 -8
  102. package/lib/trace/context-budget.js.map +1 -1
  103. package/lib/trace/turn-trace-format.js +4 -1
  104. package/lib/trace/turn-trace-format.js.map +1 -1
  105. package/lib/trace/turn-trace.d.ts +6 -0
  106. package/lib/trace/turn-trace.js +8 -2
  107. package/lib/trace/turn-trace.js.map +1 -1
  108. package/lib/trace/turn-usage-log.d.ts +14 -0
  109. package/lib/trace/turn-usage-log.js +83 -0
  110. package/lib/trace/turn-usage-log.js.map +1 -0
  111. package/lib/usage/usage-store.d.ts +199 -0
  112. package/lib/usage/usage-store.js +121 -0
  113. package/lib/usage/usage-store.js.map +1 -0
  114. package/package.json +12 -12
@@ -1 +1 @@
1
- {"version":3,"file":"policy-facade.js","sourceRoot":"","sources":["../../src/security/policy-facade.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 统一工具安全策略门面(policy facade)\n *\n * 本模块把各策略层声明为一张按 priority 排序的策略表,`runToolPolicies` 依序执行,\n * 第一个终结决策(deny 或需 Owner 确认)即短路返回,`decisions` 记录已执行层。\n *\n * 层顺序:\n * role-gate → bash-command-safety → dangerous-tool-approval → bash-sensitive-read\n * → file-permission-matrix(文件写类 + 显式 read)→ bash-file-permission\n * → memory-write-path(写类且有 filePath)→ sensitive-path(有 filePath)\n * → blocked-device-path(写类且有 filePath;read_file 经 devicePathGuard 显式启用)\n * → workspace-access(写类且有 filePath)\n * → exec-policy(有 command 且给 config)\n *\n * memory-write-path / blocked-device-path / workspace-access 仅对写操作生效;\n * read 类工具由文件权限矩阵和显式 devicePathGuard 约束。\n */\n\nimport { NETWORK_DISABLED_REASON, NETWORK_TOOL_NAMES } from './network-authority.js';\nimport type { Message } from '@zhin.js/core';\nimport type { ZhinAgentConfig } from '../config/index.js';\nimport { checkMemoryWritePath } from '../memory-layers.js';\nimport {\n TRUST_LEVEL_POLICY,\n TRUST_RESTRICTION_REASON,\n isToolAboveTrust,\n resolveTurnTrustLevel,\n} from './trust-level.js';\nimport {\n checkFileAccess,\n checkBashCommandSafety,\n extractBashReadPaths,\n isBlockedDevicePath,\n} from './file-policy.js';\nimport {\n checkBashFilePermission,\n checkFilePermission,\n formatFilePermissionMessage,\n toolRequesterRoleToFileRole,\n type FileOperation,\n type FilePermissionResult,\n} from './file-role-policy.js';\nimport {\n checkBashSensitiveReadAccess,\n checkDangerousToolAccess,\n checkFileToolAccess,\n checkSensitiveFilePathAccess,\n toDenyError,\n toOwnerSignal,\n type DangerousToolDecision,\n type FileToolName,\n} from './dangerous-tool-policy.js';\nimport { checkExecPolicyWithOptions, checkTurnExecPolicy, checkUnattendedExecPreset } from './exec-policy.js';\nimport { resolveToolRequesterRole, type ToolRequesterRole } from './owner-approval-runtime.js';\nimport {\n checkUrlNetworkAccess,\n extractUrlsFromCommand,\n NETWORK_COMMAND_PATTERNS,\n} from './network-policy.js';\nimport type { ToolNetworkPolicy } from './network-policy-context.js';\nimport type { ToolDescriptor } from '@zhin.js/tool';\nimport type { TurnIngress } from '../turn/turn-ingress.js';\nimport { authorizeTurnFileInput } from './turn-file-authority.js';\n\n// ── 输入 / 输出类型 ─────────────────────────────────────────────────\n\nexport interface ToolPolicyInput {\n /** 工具名:'read_file'|'edit_file'|'write_file'|'glob'|'list_dir'|'bash'|'web_fetch'|... */\n toolName: string;\n /** 目标路径(文件类工具;已按工具既有行为 expandHome 后传入) */\n filePath?: string;\n /**\n * 未 expandHome 的原始路径,仅供 file-permission-matrix 层使用\n * (旧实现中权限矩阵在 expandHome 之前执行,为保持等价单独透传)。\n */\n rawFilePath?: string;\n /** Workspace root used to resolve and authorize relative file paths. */\n workspaceDir?: string;\n /** 覆盖从 toolName 推导的文件操作类型(edit_file→update,write_file→create) */\n fileOperation?: FileOperation;\n /** bash 命令(exec-policy 与 bash 三层用) */\n command?: string;\n /** 读类工具显式启用 blocked-device-path 层。 */\n devicePathGuard?: boolean;\n commMessage?: Message;\n /** exec policy 用配置 */\n config?: Required<ZhinAgentConfig>;\n /** URL-bearing unattended tool invocation. */\n networkUrl?: string;\n /** Optional generic network constraint supplied by an execution harness. */\n networkPolicy?: ToolNetworkPolicy;\n}\n\nexport interface ToolPolicyDecision {\n allowed: boolean;\n reason?: string;\n /** 需 Owner/二次确认(gate);allowed=true 时同样终结执行 */\n needsOwnerApproval?: boolean;\n role?: ToolRequesterRole;\n /** 各层原始结果(FilePermissionResult / DangerousToolDecision / ExecPolicyResult ...) */\n payload?: unknown;\n}\n\nexport interface ToolPolicyLayerRecord {\n policy: string;\n decision: ToolPolicyDecision;\n}\n\nexport interface ToolPolicyResult extends ToolPolicyDecision {\n /** 产生终结决策的策略名(全部通过时无) */\n deniedBy?: string;\n /** 已执行层的决策记录 */\n decisions: ReadonlyArray<ToolPolicyLayerRecord>;\n}\n\nexport type TurnToolPolicyDecision =\n | Readonly<{ status: 'allowed'; input: Readonly<Record<string, unknown>> }>\n | Readonly<{ status: 'denied'; policy: string; reason: string }>\n | Readonly<{ status: 'approval_required'; policy: string; reason: string; input: Readonly<Record<string, unknown>> }>;\n\nexport interface TurnToolPolicyInput {\n readonly turn: TurnIngress;\n readonly tool: ToolDescriptor;\n readonly input: Readonly<Record<string, unknown>>;\n}\n\n/** Generic, execution-domain-neutral policy facade for canonical Turn tools. */\nexport async function runTurnToolPolicies(input: TurnToolPolicyInput): Promise<TurnToolPolicyDecision> {\n const requesterRole = resolveTurnRequesterRole(input.turn);\n // Defense in depth for #726: also covers tools that reach a turn later (e.g. deferred loads).\n if (isToolAboveTrust(input.tool, resolveTurnTrustLevel(input.turn))) {\n return Object.freeze({\n status: 'denied',\n policy: TRUST_LEVEL_POLICY,\n reason: TRUST_RESTRICTION_REASON,\n });\n }\n const networkDecision = checkTurnNetworkPolicy(input);\n if (networkDecision) return networkDecision;\n const fileOperation = resolveTurnFileOperation(input.tool.name);\n let authorizedInput = input.input;\n if (fileOperation) {\n const fileAuthority = await authorizeTurnFileInput(\n input.tool.name,\n input.input,\n input.turn.policy.filesystem?.workspaceRoot,\n input.turn.policy.filesystem?.workingDirectory,\n );\n if (!fileAuthority.allowed) {\n return Object.freeze({ status: 'denied', policy: 'workspace-access', reason: fileAuthority.reason });\n }\n authorizedInput = fileAuthority.input;\n const path = stringArgument(authorizedInput, 'path', 'file_path', 'filePath');\n const permission = checkFilePermission(\n toolRequesterRoleToFileRole(requesterRole),\n fileOperation,\n path,\n );\n const decision = turnFilePermissionDecision(permission, 'file-permission-matrix', authorizedInput);\n if (decision) return decision;\n if (input.turn.policy.filesystem?.access === 'read-only' && fileOperation !== 'read') {\n return Object.freeze({\n status: 'denied',\n policy: 'workspace-access',\n reason: `Turn workspace is read-only; ${fileOperation} is not allowed`,\n });\n }\n }\n if (input.tool.name === 'bash') {\n const cwdAuthority = await authorizeTurnFileInput(\n 'bash',\n authorizedInput,\n input.turn.policy.filesystem?.workspaceRoot,\n input.turn.policy.filesystem?.workingDirectory,\n );\n if (!cwdAuthority.allowed) {\n return Object.freeze({ status: 'denied', policy: 'workspace-access', reason: cwdAuthority.reason });\n }\n authorizedInput = cwdAuthority.input;\n const command = stringArgument(authorizedInput, 'command');\n if (command) {\n if (input.turn.policy.shell?.preset) {\n const exec = checkUnattendedExecPreset(command, input.turn.policy.shell.preset);\n if (!exec.allowed) {\n return Object.freeze({\n status: 'denied',\n policy: 'exec-policy',\n reason: exec.reason ?? `command denied by ${input.turn.policy.shell.preset} preset`,\n });\n }\n }\n if (input.turn.policy.shell?.security) {\n const exec = checkTurnExecPolicy(command, input.turn.policy.shell);\n if (!exec.allowed) {\n if (exec.needsApproval) {\n return Object.freeze({\n status: 'approval_required',\n policy: 'exec-policy',\n reason: exec.reason ?? 'shell command requires approval',\n input: authorizedInput,\n });\n }\n return Object.freeze({\n status: 'denied',\n policy: 'exec-policy',\n reason: exec.reason ?? 'shell command denied',\n });\n }\n }\n const safety = checkBashCommandSafety(command);\n if (!safety.safe) {\n return Object.freeze({\n status: 'denied',\n policy: 'bash-command-safety',\n reason: safety.reason ?? 'unsafe Bash command',\n });\n }\n for (const path of extractBashReadPaths(command)) {\n const sensitiveRead = checkFilePermission(\n toolRequesterRoleToFileRole(requesterRole),\n 'read',\n path,\n );\n const decision = turnFilePermissionDecision(sensitiveRead, 'bash-sensitive-read', input.input);\n if (decision) return decision;\n }\n const permission = checkBashFilePermission(toolRequesterRoleToFileRole(requesterRole), command);\n const decision = turnFilePermissionDecision(permission, 'bash-file-permission', authorizedInput);\n if (decision) return decision;\n if (input.turn.policy.filesystem?.access === 'read-only' && permission.fileOperation !== null && permission.fileOperation !== 'read') {\n return Object.freeze({\n status: 'denied',\n policy: 'workspace-access',\n reason: `Turn workspace is read-only; shell ${permission.fileOperation} is not allowed`,\n });\n }\n }\n }\n if (requiresDeclarativeApproval(input.tool)) {\n return Object.freeze({\n status: 'approval_required',\n policy: 'approval',\n reason: `tool approval policy is ${input.tool.requiresApproval}`,\n input: authorizedInput,\n });\n }\n return Object.freeze({ status: 'allowed', input: authorizedInput });\n}\n\nfunction requiresDeclarativeApproval(tool: ToolDescriptor): boolean {\n if (tool.requiresApproval === 'never') return false;\n if (tool.requiresApproval === 'always' || tool.requiresApproval === 'once') return true;\n return !hasCanonicalRiskPolicy(tool.name);\n}\n\n/**\n * Canonical tools reach this point only after their concrete network, file, or\n * shell operation passed the dedicated policy layers above. Unknown plugin\n * tools remain fail-closed under the default `on-risk` declaration.\n */\nfunction hasCanonicalRiskPolicy(toolName: string): boolean {\n return toolName === 'bash'\n || toolName === 'web_fetch'\n || toolName === 'web_search'\n || resolveTurnFileOperation(toolName) !== undefined;\n}\n\nfunction checkTurnNetworkPolicy(input: TurnToolPolicyInput): TurnToolPolicyDecision | undefined {\n const command = input.tool.name === 'bash' ? stringArgument(input.input, 'command') : undefined;\n const isNetworkCommand = Boolean(command && NETWORK_COMMAND_PATTERNS.some((pattern) => pattern.test(command)));\n const isNetworkTool = NETWORK_TOOL_NAMES.has(input.tool.name);\n if (!isNetworkCommand && !isNetworkTool) return undefined;\n\n const policy = input.turn.policy.network;\n if (!policy?.enabled) {\n return Object.freeze({\n status: 'denied',\n policy: 'network-access',\n reason: NETWORK_DISABLED_REASON,\n });\n }\n\n const urls = input.tool.name === 'web_fetch'\n ? [stringArgument(input.input, 'url')].filter((value): value is string => Boolean(value))\n : command\n ? extractUrlsFromCommand(command)\n : [];\n if ((input.tool.name === 'web_fetch' || isNetworkCommand) && urls.length === 0) {\n return Object.freeze({\n status: 'denied',\n policy: 'network-access',\n reason: 'Network access requires a verifiable absolute URL',\n });\n }\n for (const url of urls) {\n const decision = checkUrlNetworkAccess(url, {\n httpsOnly: policy.httpsOnly,\n allowedDomains: [...(policy.allowedDomains ?? [])],\n });\n if (!decision.allowed) {\n return Object.freeze({\n status: 'denied',\n policy: 'network-access',\n reason: decision.reason ?? 'Network target denied',\n });\n }\n }\n return undefined;\n}\n\nfunction turnFilePermissionDecision(\n permission: FilePermissionResult,\n policy: string,\n input: Readonly<Record<string, unknown>>,\n): TurnToolPolicyDecision | undefined {\n if (!permission.allowed) {\n return Object.freeze({\n status: 'denied',\n policy,\n reason: permission.reason ?? `file ${permission.operation} denied`,\n });\n }\n if (permission.needsConfirmation || permission.needsOwnerConfirmation) {\n return Object.freeze({\n status: 'approval_required',\n policy,\n reason: permission.reason ?? `file ${permission.operation} requires approval`,\n input,\n });\n }\n return undefined;\n}\n\nfunction resolveTurnFileOperation(toolName: string): FileOperation | undefined {\n if (toolName === 'write_file') return 'create';\n if (toolName === 'edit_file') return 'update';\n if (toolName === 'read_file' || toolName === 'list_dir' || toolName === 'glob'\n || toolName === 'grep') return 'read';\n return undefined;\n}\n\nfunction resolveTurnRequesterRole(turn: TurnIngress): ToolRequesterRole {\n if (turn.principal.roles.includes('master')) return 'master';\n if (turn.principal.roles.includes('trusted')) return 'trusted';\n return 'other';\n}\n\nfunction stringArgument(\n input: Readonly<Record<string, unknown>>,\n ...names: readonly string[]\n): string | undefined {\n for (const name of names) {\n const value = input[name];\n if (typeof value === 'string' && value.trim()) return value;\n }\n return undefined;\n}\n\n// ── 声明式策略表 ────────────────────────────────────────────────────\n\nexport interface ToolPolicyLayer {\n name: string;\n priority: number;\n applies(input: ToolPolicyInput): boolean;\n check(input: ToolPolicyInput, prior: ReadonlyArray<ToolPolicyLayerRecord>): ToolPolicyDecision;\n}\n\nconst FILE_TOOL_NAMES = new Set(['read_file', 'list_dir', 'glob', 'grep', 'write_file', 'edit_file']);\nconst DANGEROUS_TOOL_NAMES = new Set(['write_file', 'edit_file', 'web_fetch']);\nconst WRITE_OPERATIONS = new Set<FileOperation>(['create', 'update', 'delete']);\n\n/** 解析文件操作类型:显式 fileOperation 优先,其次按 toolName 推导(仅写类工具) */\nfunction resolveFileOperation(input: ToolPolicyInput): FileOperation | undefined {\n if (input.fileOperation) return input.fileOperation;\n if (input.toolName === 'edit_file') return 'update';\n if (input.toolName === 'write_file') return 'create';\n return undefined;\n}\n\n/** 是否为文件写类调用(memory/device/workspace 层的生效条件) */\nfunction isWriteAccess(input: ToolPolicyInput): boolean {\n const op = resolveFileOperation(input);\n return Boolean(input.filePath) && op !== undefined && WRITE_OPERATIONS.has(op);\n}\n\nfunction fromDangerousDecision(d: DangerousToolDecision): ToolPolicyDecision {\n return {\n allowed: d.allowed,\n reason: d.reason,\n needsOwnerApproval: d.needsOwnerApproval,\n role: d.role,\n payload: d,\n };\n}\n\n/** 是否为 checkMemoryWritePath 已放行的记忆写路径(session 任意;global/platform 仅 master) */\nfunction isPermittedMemoryToolWrite(input: ToolPolicyInput): boolean {\n const filePath = input.rawFilePath ?? input.filePath;\n if (!filePath || !isWriteAccess(input)) return false;\n const decision = checkMemoryWritePath(filePath, input.commMessage, input.workspaceDir);\n return decision.scope !== 'none' && decision.allowed;\n}\n\nfunction sortByPriority(layers: ToolPolicyLayer[]): ToolPolicyLayer[] {\n return layers.sort((a, b) => a.priority - b.priority);\n}\n\nconst TOOL_POLICIES: ToolPolicyLayer[] = sortByPriority([\n {\n name: 'network-access',\n priority: 5,\n applies: (input) => Boolean(input.networkPolicy && input.networkUrl),\n check: (input) => {\n const result = checkUrlNetworkAccess(input.networkUrl!, {\n httpsOnly: input.networkPolicy!.httpsOnly,\n allowedDomains: [...(input.networkPolicy!.allowedDomains ?? [])],\n });\n return { allowed: result.allowed, reason: result.reason, payload: result };\n },\n },\n {\n name: 'role-gate',\n priority: 10,\n applies: (input) => FILE_TOOL_NAMES.has(input.toolName),\n check: (input) => {\n // 与 checkMemoryWritePath 对齐:会话记忆写对任意角色放行(避免矩阵/危险工具层先拒)\n if (isPermittedMemoryToolWrite(input)) {\n const role = input.commMessage\n ? resolveToolRequesterRole(input.commMessage)\n : 'unknown';\n return { allowed: true, role };\n }\n return fromDangerousDecision(checkFileToolAccess(input.toolName as FileToolName, input.commMessage));\n },\n },\n {\n name: 'bash-command-safety',\n priority: 15,\n applies: (input) => input.toolName === 'bash' && Boolean(input.command),\n check: (input) => {\n const safety = checkBashCommandSafety(input.command!);\n return { allowed: safety.safe, reason: safety.reason, payload: safety };\n },\n },\n {\n name: 'dangerous-tool-approval',\n priority: 20,\n applies: (input) => DANGEROUS_TOOL_NAMES.has(input.toolName),\n check: (input) => {\n if (isPermittedMemoryToolWrite(input)) {\n const role = input.commMessage\n ? resolveToolRequesterRole(input.commMessage)\n : 'unknown';\n return { allowed: true, role };\n }\n return fromDangerousDecision(\n checkDangerousToolAccess(input.toolName as 'write_file' | 'edit_file' | 'web_fetch', input.commMessage),\n );\n },\n },\n {\n name: 'bash-sensitive-read',\n priority: 25,\n applies: (input) => input.toolName === 'bash' && Boolean(input.command),\n check: (input) => fromDangerousDecision(\n checkBashSensitiveReadAccess(input.command!, input.commMessage, input.workspaceDir),\n ),\n },\n {\n name: 'file-permission-matrix',\n priority: 30,\n applies: (input) => resolveFileOperation(input) !== undefined,\n check: (input, prior) => {\n const operation = resolveFileOperation(input)!;\n const roleGate = prior.find((r) => r.policy === 'role-gate');\n const requesterRole: ToolRequesterRole = roleGate?.decision.role ?? 'unknown';\n if (isPermittedMemoryToolWrite(input)) {\n return {\n allowed: true,\n role: requesterRole,\n payload: {\n allowed: true,\n role: toolRequesterRoleToFileRole(requesterRole),\n operation,\n } satisfies FilePermissionResult,\n };\n }\n const fileRole = toolRequesterRoleToFileRole(requesterRole);\n const permResult = checkFilePermission(fileRole, operation, input.rawFilePath ?? input.filePath);\n return {\n allowed: permResult.allowed,\n reason: permResult.reason,\n needsOwnerApproval:\n permResult.allowed && (permResult.needsConfirmation === true || permResult.needsOwnerConfirmation === true)\n ? true\n : undefined,\n role: requesterRole,\n payload: permResult,\n };\n },\n },\n {\n name: 'bash-file-permission',\n priority: 35,\n applies: (input) => input.toolName === 'bash' && Boolean(input.command),\n check: (input) => {\n const requesterRole: ToolRequesterRole =\n input.commMessage\n ? resolveToolRequesterRole(input.commMessage)\n : 'unknown';\n const role = toolRequesterRoleToFileRole(requesterRole);\n const permResult = checkBashFilePermission(role, input.command!);\n return {\n allowed: permResult.allowed,\n reason: permResult.reason,\n needsOwnerApproval:\n permResult.allowed && (permResult.needsConfirmation === true || permResult.needsOwnerConfirmation === true)\n ? true\n : undefined,\n role: requesterRole,\n payload: permResult,\n };\n },\n },\n {\n name: 'memory-write-path',\n priority: 40,\n applies: (input) => isWriteAccess(input),\n check: (input) => {\n const d = checkMemoryWritePath(input.filePath!, input.commMessage, input.workspaceDir);\n return { allowed: d.allowed, reason: d.reason, payload: d };\n },\n },\n {\n name: 'sensitive-path',\n priority: 50,\n applies: (input) => Boolean(input.filePath),\n check: (input) =>\n fromDangerousDecision(\n checkSensitiveFilePathAccess(\n input.toolName as FileToolName,\n input.filePath!,\n input.commMessage,\n input.workspaceDir,\n ),\n ),\n },\n {\n name: 'blocked-device-path',\n priority: 60,\n applies: (input) => Boolean(input.filePath) && (isWriteAccess(input) || input.devicePathGuard === true),\n check: (input) => {\n if (!isBlockedDevicePath(input.filePath!)) return { allowed: true };\n // 写类措辞与 read_file 读类措辞分别对齐各自旧链文案\n const reason = isWriteAccess(input)\n ? `禁止访问设备路径: ${input.filePath!}`\n : `禁止读取设备文件 ${input.filePath!}(会导致进程挂起或注入攻击)`;\n return { allowed: false, reason };\n },\n },\n {\n name: 'workspace-access',\n priority: 70,\n applies: (input) => isWriteAccess(input),\n check: (input) => {\n const d = checkFileAccess(input.filePath!);\n return { allowed: d.allowed, reason: d.reason, payload: d };\n },\n },\n {\n name: 'exec-policy',\n priority: 80,\n applies: (input) => Boolean(input.command) && Boolean(input.config),\n check: (input) => {\n const r = checkExecPolicyWithOptions(input.config!, input.command!);\n return { allowed: r.allowed, reason: r.reason, needsOwnerApproval: r.needsApproval || undefined, payload: r };\n },\n },\n]);\n\n// ── 执行入口 ────────────────────────────────────────────────────────\n\n/**\n * 依 priority 顺序执行适用的策略层;\n * 第一个 `!allowed` 或 `needsOwnerApproval` 的决策即短路返回(deny 优先)。\n */\nexport function runToolPolicies(input: ToolPolicyInput): ToolPolicyResult {\n const decisions: ToolPolicyLayerRecord[] = [];\n for (const layer of TOOL_POLICIES) {\n if (!layer.applies(input)) continue;\n const decision = layer.check(input, decisions);\n decisions.push({ policy: layer.name, decision });\n if (!decision.allowed || decision.needsOwnerApproval) {\n return { ...decision, deniedBy: layer.name, decisions };\n }\n }\n return { allowed: true, decisions };\n}\n\n// ── 结果映射(复刻各工具既有对外文案) ──────────────────────────────\n\n/**\n * 把门面结果映射为工具应返回的字符串;全部通过时返回 null(工具继续执行)。\n * 文案与各工具迁移前逐层手写的返回严格一致。\n */\nexport function toolPolicyResultToMessage(result: ToolPolicyResult, toolName = ''): string | null {\n if (result.allowed && !result.needsOwnerApproval) return null;\n switch (result.deniedBy) {\n case 'file-permission-matrix':\n case 'bash-file-permission':\n // 覆盖拒绝与 needsConfirmation/needsOwnerConfirmation 两种 gate 文案\n return formatFilePermissionMessage(result.payload as FilePermissionResult, toolName);\n case 'memory-write-path':\n return `Error: ${result.reason}`;\n case 'blocked-device-path':\n return `Error: ${result.reason}`;\n case 'workspace-access':\n return `ZHIN_NEEDS_OWNER:\\n${result.reason!}\\n\\n(文件访问策略拒绝;仅 Owner 确认后在受控环境可重试或调整策略。)`;\n case 'exec-policy':\n return result.needsOwnerApproval\n ? `ZHIN_NEEDS_OWNER:\\n${result.reason ?? '该命令需要 Owner 确认。'}`\n : `Error: ${result.reason ?? '该命令已被拒绝。'}`;\n default: {\n // role-gate / dangerous-tool-approval / sensitive-path:统一走 DangerousToolDecision 文案\n const decision: DangerousToolDecision = {\n allowed: result.allowed,\n reason: result.reason,\n needsOwnerApproval: result.needsOwnerApproval,\n role: result.role ?? 'unknown',\n };\n return result.needsOwnerApproval ? toOwnerSignal(decision) : toDenyError(decision);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"policy-facade.js","sourceRoot":"","sources":["../../src/security/policy-facade.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 统一工具安全策略门面(policy facade)\n *\n * 本模块把各策略层声明为一张按 priority 排序的策略表,`runToolPolicies` 依序执行,\n * 第一个终结决策(deny 或需 Owner 确认)即短路返回,`decisions` 记录已执行层。\n *\n * 层顺序:\n * role-gate → bash-command-safety → dangerous-tool-approval → bash-sensitive-read\n * → file-permission-matrix(文件写类 + 显式 read)→ bash-file-permission\n * → memory-write-path(写类且有 filePath)→ sensitive-path(有 filePath)\n * → blocked-device-path(写类且有 filePath;read_file 经 devicePathGuard 显式启用)\n * → workspace-access(写类且有 filePath)\n * → exec-policy(有 command 且给 config)\n *\n * memory-write-path / blocked-device-path / workspace-access 仅对写操作生效;\n * read 类工具由文件权限矩阵和显式 devicePathGuard 约束。\n */\n\nimport { NETWORK_DISABLED_REASON, NETWORK_TOOL_NAMES } from './network-authority.js';\nimport type { Message } from '@zhin.js/core';\nimport type { ZhinAgentConfig } from '../config/index.js';\nimport { checkMemoryWritePath } from '../memory-layers.js';\nimport {\n TRUST_LEVEL_POLICY,\n TRUST_RESTRICTION_REASON,\n isToolAboveTrust,\n resolveTurnTrustLevel,\n} from './trust-level.js';\nimport {\n checkFileAccess,\n checkBashCommandSafety,\n extractBashReadPaths,\n isBlockedDevicePath,\n} from './file-policy.js';\nimport {\n checkBashFilePermission,\n checkFilePermission,\n formatFilePermissionMessage,\n toolRequesterRoleToFileRole,\n type FileOperation,\n type FilePermissionResult,\n} from './file-role-policy.js';\nimport {\n checkBashSensitiveReadAccess,\n checkDangerousToolAccess,\n checkFileToolAccess,\n checkSensitiveFilePathAccess,\n toDenyError,\n toOwnerSignal,\n type DangerousToolDecision,\n type FileToolName,\n} from './dangerous-tool-policy.js';\nimport { checkExecPolicyWithOptions, checkTurnExecPolicy, checkUnattendedExecPreset } from './exec-policy.js';\nimport { resolveToolRequesterRole, type ToolRequesterRole } from './owner-approval-runtime.js';\nimport {\n checkUrlNetworkAccess,\n extractUrlsFromCommand,\n NETWORK_COMMAND_PATTERNS,\n} from './network-policy.js';\nimport type { ToolNetworkPolicy } from './network-policy-context.js';\nimport type { ToolDescriptor } from '@zhin.js/tool';\nimport type { TurnIngress } from '../turn/turn-ingress.js';\nimport { authorizeTurnFileInput } from './turn-file-authority.js';\n\n// ── 输入 / 输出类型 ─────────────────────────────────────────────────\n\nexport interface ToolPolicyInput {\n /** 工具名:'read_file'|'edit_file'|'write_file'|'glob'|'list_dir'|'bash'|'web_fetch'|... */\n toolName: string;\n /** 目标路径(文件类工具;已按工具既有行为 expandHome 后传入) */\n filePath?: string;\n /**\n * 未 expandHome 的原始路径,仅供 file-permission-matrix 层使用\n * (旧实现中权限矩阵在 expandHome 之前执行,为保持等价单独透传)。\n */\n rawFilePath?: string;\n /** Workspace root used to resolve and authorize relative file paths. */\n workspaceDir?: string;\n /** 覆盖从 toolName 推导的文件操作类型(edit_file→update,write_file→create) */\n fileOperation?: FileOperation;\n /** bash 命令(exec-policy 与 bash 三层用) */\n command?: string;\n /** 读类工具显式启用 blocked-device-path 层。 */\n devicePathGuard?: boolean;\n commMessage?: Message;\n /** exec policy 用配置 */\n config?: Required<ZhinAgentConfig>;\n /** URL-bearing unattended tool invocation. */\n networkUrl?: string;\n /** Optional generic network constraint supplied by an execution harness. */\n networkPolicy?: ToolNetworkPolicy;\n}\n\nexport interface ToolPolicyDecision {\n allowed: boolean;\n reason?: string;\n /** 需 Owner/二次确认(gate);allowed=true 时同样终结执行 */\n needsOwnerApproval?: boolean;\n role?: ToolRequesterRole;\n /** 各层原始结果(FilePermissionResult / DangerousToolDecision / ExecPolicyResult ...) */\n payload?: unknown;\n}\n\nexport interface ToolPolicyLayerRecord {\n policy: string;\n decision: ToolPolicyDecision;\n}\n\nexport interface ToolPolicyResult extends ToolPolicyDecision {\n /** 产生终结决策的策略名(全部通过时无) */\n deniedBy?: string;\n /** 已执行层的决策记录 */\n decisions: ReadonlyArray<ToolPolicyLayerRecord>;\n}\n\nexport type TurnToolPolicyDecision =\n | Readonly<{ status: 'allowed'; input: Readonly<Record<string, unknown>> }>\n | Readonly<{ status: 'denied'; policy: string; reason: string }>\n | Readonly<{ status: 'approval_required'; policy: string; reason: string; input: Readonly<Record<string, unknown>> }>;\n\nexport interface TurnToolPolicyInput {\n readonly turn: TurnIngress;\n readonly tool: ToolDescriptor;\n readonly input: Readonly<Record<string, unknown>>;\n}\n\n/** Generic, execution-domain-neutral policy facade for canonical Turn tools. */\nexport async function runTurnToolPolicies(input: TurnToolPolicyInput): Promise<TurnToolPolicyDecision> {\n const requesterRole = resolveTurnRequesterRole(input.turn);\n // Defense in depth for #726: also covers tools that reach a turn later (e.g. deferred loads).\n if (isToolAboveTrust(input.tool, resolveTurnTrustLevel(input.turn))) {\n return Object.freeze({\n status: 'denied',\n policy: TRUST_LEVEL_POLICY,\n reason: TRUST_RESTRICTION_REASON,\n });\n }\n const networkDecision = checkTurnNetworkPolicy(input);\n if (networkDecision) return networkDecision;\n const fileOperation = resolveTurnFileOperation(input.tool.name);\n let authorizedInput = input.input;\n if (fileOperation) {\n const fileAuthority = await authorizeTurnFileInput(\n input.tool.name,\n input.input,\n input.turn.policy.filesystem?.workspaceRoot,\n input.turn.policy.filesystem?.workingDirectory,\n );\n if (!fileAuthority.allowed) {\n return Object.freeze({ status: 'denied', policy: 'workspace-access', reason: fileAuthority.reason });\n }\n authorizedInput = fileAuthority.input;\n const path = stringArgument(authorizedInput, 'path', 'file_path', 'filePath');\n const permission = checkFilePermission(\n toolRequesterRoleToFileRole(requesterRole),\n fileOperation,\n path,\n );\n const decision = turnFilePermissionDecision(permission, 'file-permission-matrix', authorizedInput);\n if (decision) return decision;\n if (input.turn.policy.filesystem?.access === 'read-only' && fileOperation !== 'read') {\n return Object.freeze({\n status: 'denied',\n policy: 'workspace-access',\n reason: `Turn workspace is read-only; ${fileOperation} is not allowed`,\n });\n }\n }\n if (input.tool.name === 'bash') {\n const cwdAuthority = await authorizeTurnFileInput(\n 'bash',\n authorizedInput,\n input.turn.policy.filesystem?.workspaceRoot,\n input.turn.policy.filesystem?.workingDirectory,\n );\n if (!cwdAuthority.allowed) {\n return Object.freeze({ status: 'denied', policy: 'workspace-access', reason: cwdAuthority.reason });\n }\n authorizedInput = cwdAuthority.input;\n const command = stringArgument(authorizedInput, 'command');\n if (command) {\n if (input.turn.policy.shell?.preset) {\n const exec = checkUnattendedExecPreset(command, input.turn.policy.shell.preset);\n if (!exec.allowed) {\n return Object.freeze({\n status: 'denied',\n policy: 'exec-policy',\n reason: exec.reason ?? `command denied by ${input.turn.policy.shell.preset} preset`,\n });\n }\n }\n if (input.turn.policy.shell?.security) {\n const exec = checkTurnExecPolicy(command, input.turn.policy.shell);\n if (!exec.allowed) {\n if (exec.needsApproval) {\n return Object.freeze({\n status: 'approval_required',\n policy: 'exec-policy',\n reason: exec.reason ?? 'shell command requires approval',\n input: authorizedInput,\n });\n }\n return Object.freeze({\n status: 'denied',\n policy: 'exec-policy',\n reason: exec.reason ?? 'shell command denied',\n });\n }\n }\n const safety = checkBashCommandSafety(command);\n if (!safety.safe) {\n return Object.freeze({\n status: 'denied',\n policy: 'bash-command-safety',\n reason: safety.reason ?? 'unsafe Bash command',\n });\n }\n for (const path of extractBashReadPaths(command)) {\n const sensitiveRead = checkFilePermission(\n toolRequesterRoleToFileRole(requesterRole),\n 'read',\n path,\n );\n const decision = turnFilePermissionDecision(sensitiveRead, 'bash-sensitive-read', input.input);\n if (decision) return decision;\n }\n const permission = checkBashFilePermission(toolRequesterRoleToFileRole(requesterRole), command);\n const decision = turnFilePermissionDecision(permission, 'bash-file-permission', authorizedInput);\n if (decision) return decision;\n if (input.turn.policy.filesystem?.access === 'read-only' && permission.fileOperation !== null && permission.fileOperation !== 'read') {\n return Object.freeze({\n status: 'denied',\n policy: 'workspace-access',\n reason: `Turn workspace is read-only; shell ${permission.fileOperation} is not allowed`,\n });\n }\n }\n }\n if (requiresDeclarativeApproval(input.tool)) {\n return Object.freeze({\n status: 'approval_required',\n policy: 'approval',\n reason: `tool approval policy is ${input.tool.requiresApproval}`,\n input: authorizedInput,\n });\n }\n return Object.freeze({ status: 'allowed', input: authorizedInput });\n}\n\nfunction requiresDeclarativeApproval(tool: ToolDescriptor): boolean {\n if (tool.requiresApproval === 'never') return false;\n if (tool.requiresApproval === 'always' || tool.requiresApproval === 'once' || tool.requiresApproval === 'human') return true;\n return !hasCanonicalRiskPolicy(tool.name);\n}\n\n/**\n * Canonical tools reach this point only after their concrete network, file, or\n * shell operation passed the dedicated policy layers above. Unknown plugin\n * tools remain fail-closed under the default `on-risk` declaration.\n */\nfunction hasCanonicalRiskPolicy(toolName: string): boolean {\n return toolName === 'bash'\n || toolName === 'web_fetch'\n || toolName === 'web_search'\n || resolveTurnFileOperation(toolName) !== undefined;\n}\n\nfunction checkTurnNetworkPolicy(input: TurnToolPolicyInput): TurnToolPolicyDecision | undefined {\n const command = input.tool.name === 'bash' ? stringArgument(input.input, 'command') : undefined;\n const isNetworkCommand = Boolean(command && NETWORK_COMMAND_PATTERNS.some((pattern) => pattern.test(command)));\n const isNetworkTool = NETWORK_TOOL_NAMES.has(input.tool.name);\n if (!isNetworkCommand && !isNetworkTool) return undefined;\n\n const policy = input.turn.policy.network;\n if (!policy?.enabled) {\n return Object.freeze({\n status: 'denied',\n policy: 'network-access',\n reason: NETWORK_DISABLED_REASON,\n });\n }\n\n const urls = input.tool.name === 'web_fetch'\n ? [stringArgument(input.input, 'url')].filter((value): value is string => Boolean(value))\n : command\n ? extractUrlsFromCommand(command)\n : [];\n if ((input.tool.name === 'web_fetch' || isNetworkCommand) && urls.length === 0) {\n return Object.freeze({\n status: 'denied',\n policy: 'network-access',\n reason: 'Network access requires a verifiable absolute URL',\n });\n }\n for (const url of urls) {\n const decision = checkUrlNetworkAccess(url, {\n httpsOnly: policy.httpsOnly,\n allowedDomains: [...(policy.allowedDomains ?? [])],\n });\n if (!decision.allowed) {\n return Object.freeze({\n status: 'denied',\n policy: 'network-access',\n reason: decision.reason ?? 'Network target denied',\n });\n }\n }\n return undefined;\n}\n\nfunction turnFilePermissionDecision(\n permission: FilePermissionResult,\n policy: string,\n input: Readonly<Record<string, unknown>>,\n): TurnToolPolicyDecision | undefined {\n if (!permission.allowed) {\n return Object.freeze({\n status: 'denied',\n policy,\n reason: permission.reason ?? `file ${permission.operation} denied`,\n });\n }\n if (permission.needsConfirmation || permission.needsOwnerConfirmation) {\n return Object.freeze({\n status: 'approval_required',\n policy,\n reason: permission.reason ?? `file ${permission.operation} requires approval`,\n input,\n });\n }\n return undefined;\n}\n\nfunction resolveTurnFileOperation(toolName: string): FileOperation | undefined {\n if (toolName === 'write_file') return 'create';\n if (toolName === 'edit_file') return 'update';\n if (toolName === 'read_file' || toolName === 'list_dir' || toolName === 'glob'\n || toolName === 'grep') return 'read';\n return undefined;\n}\n\nfunction resolveTurnRequesterRole(turn: TurnIngress): ToolRequesterRole {\n if (turn.principal.roles.includes('master')) return 'master';\n if (turn.principal.roles.includes('trusted')) return 'trusted';\n return 'other';\n}\n\nfunction stringArgument(\n input: Readonly<Record<string, unknown>>,\n ...names: readonly string[]\n): string | undefined {\n for (const name of names) {\n const value = input[name];\n if (typeof value === 'string' && value.trim()) return value;\n }\n return undefined;\n}\n\n// ── 声明式策略表 ────────────────────────────────────────────────────\n\nexport interface ToolPolicyLayer {\n name: string;\n priority: number;\n applies(input: ToolPolicyInput): boolean;\n check(input: ToolPolicyInput, prior: ReadonlyArray<ToolPolicyLayerRecord>): ToolPolicyDecision;\n}\n\nconst FILE_TOOL_NAMES = new Set(['read_file', 'list_dir', 'glob', 'grep', 'write_file', 'edit_file']);\nconst DANGEROUS_TOOL_NAMES = new Set(['write_file', 'edit_file', 'web_fetch']);\nconst WRITE_OPERATIONS = new Set<FileOperation>(['create', 'update', 'delete']);\n\n/** 解析文件操作类型:显式 fileOperation 优先,其次按 toolName 推导(仅写类工具) */\nfunction resolveFileOperation(input: ToolPolicyInput): FileOperation | undefined {\n if (input.fileOperation) return input.fileOperation;\n if (input.toolName === 'edit_file') return 'update';\n if (input.toolName === 'write_file') return 'create';\n return undefined;\n}\n\n/** 是否为文件写类调用(memory/device/workspace 层的生效条件) */\nfunction isWriteAccess(input: ToolPolicyInput): boolean {\n const op = resolveFileOperation(input);\n return Boolean(input.filePath) && op !== undefined && WRITE_OPERATIONS.has(op);\n}\n\nfunction fromDangerousDecision(d: DangerousToolDecision): ToolPolicyDecision {\n return {\n allowed: d.allowed,\n reason: d.reason,\n needsOwnerApproval: d.needsOwnerApproval,\n role: d.role,\n payload: d,\n };\n}\n\n/** 是否为 checkMemoryWritePath 已放行的记忆写路径(session 任意;global/platform 仅 master) */\nfunction isPermittedMemoryToolWrite(input: ToolPolicyInput): boolean {\n const filePath = input.rawFilePath ?? input.filePath;\n if (!filePath || !isWriteAccess(input)) return false;\n const decision = checkMemoryWritePath(filePath, input.commMessage, input.workspaceDir);\n return decision.scope !== 'none' && decision.allowed;\n}\n\nfunction sortByPriority(layers: ToolPolicyLayer[]): ToolPolicyLayer[] {\n return layers.sort((a, b) => a.priority - b.priority);\n}\n\nconst TOOL_POLICIES: ToolPolicyLayer[] = sortByPriority([\n {\n name: 'network-access',\n priority: 5,\n applies: (input) => Boolean(input.networkPolicy && input.networkUrl),\n check: (input) => {\n const result = checkUrlNetworkAccess(input.networkUrl!, {\n httpsOnly: input.networkPolicy!.httpsOnly,\n allowedDomains: [...(input.networkPolicy!.allowedDomains ?? [])],\n });\n return { allowed: result.allowed, reason: result.reason, payload: result };\n },\n },\n {\n name: 'role-gate',\n priority: 10,\n applies: (input) => FILE_TOOL_NAMES.has(input.toolName),\n check: (input) => {\n // 与 checkMemoryWritePath 对齐:会话记忆写对任意角色放行(避免矩阵/危险工具层先拒)\n if (isPermittedMemoryToolWrite(input)) {\n const role = input.commMessage\n ? resolveToolRequesterRole(input.commMessage)\n : 'unknown';\n return { allowed: true, role };\n }\n return fromDangerousDecision(checkFileToolAccess(input.toolName as FileToolName, input.commMessage));\n },\n },\n {\n name: 'bash-command-safety',\n priority: 15,\n applies: (input) => input.toolName === 'bash' && Boolean(input.command),\n check: (input) => {\n const safety = checkBashCommandSafety(input.command!);\n return { allowed: safety.safe, reason: safety.reason, payload: safety };\n },\n },\n {\n name: 'dangerous-tool-approval',\n priority: 20,\n applies: (input) => DANGEROUS_TOOL_NAMES.has(input.toolName),\n check: (input) => {\n if (isPermittedMemoryToolWrite(input)) {\n const role = input.commMessage\n ? resolveToolRequesterRole(input.commMessage)\n : 'unknown';\n return { allowed: true, role };\n }\n return fromDangerousDecision(\n checkDangerousToolAccess(input.toolName as 'write_file' | 'edit_file' | 'web_fetch', input.commMessage),\n );\n },\n },\n {\n name: 'bash-sensitive-read',\n priority: 25,\n applies: (input) => input.toolName === 'bash' && Boolean(input.command),\n check: (input) => fromDangerousDecision(\n checkBashSensitiveReadAccess(input.command!, input.commMessage, input.workspaceDir),\n ),\n },\n {\n name: 'file-permission-matrix',\n priority: 30,\n applies: (input) => resolveFileOperation(input) !== undefined,\n check: (input, prior) => {\n const operation = resolveFileOperation(input)!;\n const roleGate = prior.find((r) => r.policy === 'role-gate');\n const requesterRole: ToolRequesterRole = roleGate?.decision.role ?? 'unknown';\n if (isPermittedMemoryToolWrite(input)) {\n return {\n allowed: true,\n role: requesterRole,\n payload: {\n allowed: true,\n role: toolRequesterRoleToFileRole(requesterRole),\n operation,\n } satisfies FilePermissionResult,\n };\n }\n const fileRole = toolRequesterRoleToFileRole(requesterRole);\n const permResult = checkFilePermission(fileRole, operation, input.rawFilePath ?? input.filePath);\n return {\n allowed: permResult.allowed,\n reason: permResult.reason,\n needsOwnerApproval:\n permResult.allowed && (permResult.needsConfirmation === true || permResult.needsOwnerConfirmation === true)\n ? true\n : undefined,\n role: requesterRole,\n payload: permResult,\n };\n },\n },\n {\n name: 'bash-file-permission',\n priority: 35,\n applies: (input) => input.toolName === 'bash' && Boolean(input.command),\n check: (input) => {\n const requesterRole: ToolRequesterRole =\n input.commMessage\n ? resolveToolRequesterRole(input.commMessage)\n : 'unknown';\n const role = toolRequesterRoleToFileRole(requesterRole);\n const permResult = checkBashFilePermission(role, input.command!);\n return {\n allowed: permResult.allowed,\n reason: permResult.reason,\n needsOwnerApproval:\n permResult.allowed && (permResult.needsConfirmation === true || permResult.needsOwnerConfirmation === true)\n ? true\n : undefined,\n role: requesterRole,\n payload: permResult,\n };\n },\n },\n {\n name: 'memory-write-path',\n priority: 40,\n applies: (input) => isWriteAccess(input),\n check: (input) => {\n const d = checkMemoryWritePath(input.filePath!, input.commMessage, input.workspaceDir);\n return { allowed: d.allowed, reason: d.reason, payload: d };\n },\n },\n {\n name: 'sensitive-path',\n priority: 50,\n applies: (input) => Boolean(input.filePath),\n check: (input) =>\n fromDangerousDecision(\n checkSensitiveFilePathAccess(\n input.toolName as FileToolName,\n input.filePath!,\n input.commMessage,\n input.workspaceDir,\n ),\n ),\n },\n {\n name: 'blocked-device-path',\n priority: 60,\n applies: (input) => Boolean(input.filePath) && (isWriteAccess(input) || input.devicePathGuard === true),\n check: (input) => {\n if (!isBlockedDevicePath(input.filePath!)) return { allowed: true };\n // 写类措辞与 read_file 读类措辞分别对齐各自旧链文案\n const reason = isWriteAccess(input)\n ? `禁止访问设备路径: ${input.filePath!}`\n : `禁止读取设备文件 ${input.filePath!}(会导致进程挂起或注入攻击)`;\n return { allowed: false, reason };\n },\n },\n {\n name: 'workspace-access',\n priority: 70,\n applies: (input) => isWriteAccess(input),\n check: (input) => {\n const d = checkFileAccess(input.filePath!);\n return { allowed: d.allowed, reason: d.reason, payload: d };\n },\n },\n {\n name: 'exec-policy',\n priority: 80,\n applies: (input) => Boolean(input.command) && Boolean(input.config),\n check: (input) => {\n const r = checkExecPolicyWithOptions(input.config!, input.command!);\n return { allowed: r.allowed, reason: r.reason, needsOwnerApproval: r.needsApproval || undefined, payload: r };\n },\n },\n]);\n\n// ── 执行入口 ────────────────────────────────────────────────────────\n\n/**\n * 依 priority 顺序执行适用的策略层;\n * 第一个 `!allowed` 或 `needsOwnerApproval` 的决策即短路返回(deny 优先)。\n */\nexport function runToolPolicies(input: ToolPolicyInput): ToolPolicyResult {\n const decisions: ToolPolicyLayerRecord[] = [];\n for (const layer of TOOL_POLICIES) {\n if (!layer.applies(input)) continue;\n const decision = layer.check(input, decisions);\n decisions.push({ policy: layer.name, decision });\n if (!decision.allowed || decision.needsOwnerApproval) {\n return { ...decision, deniedBy: layer.name, decisions };\n }\n }\n return { allowed: true, decisions };\n}\n\n// ── 结果映射(复刻各工具既有对外文案) ──────────────────────────────\n\n/**\n * 把门面结果映射为工具应返回的字符串;全部通过时返回 null(工具继续执行)。\n * 文案与各工具迁移前逐层手写的返回严格一致。\n */\nexport function toolPolicyResultToMessage(result: ToolPolicyResult, toolName = ''): string | null {\n if (result.allowed && !result.needsOwnerApproval) return null;\n switch (result.deniedBy) {\n case 'file-permission-matrix':\n case 'bash-file-permission':\n // 覆盖拒绝与 needsConfirmation/needsOwnerConfirmation 两种 gate 文案\n return formatFilePermissionMessage(result.payload as FilePermissionResult, toolName);\n case 'memory-write-path':\n return `Error: ${result.reason}`;\n case 'blocked-device-path':\n return `Error: ${result.reason}`;\n case 'workspace-access':\n return `ZHIN_NEEDS_OWNER:\\n${result.reason!}\\n\\n(文件访问策略拒绝;仅 Owner 确认后在受控环境可重试或调整策略。)`;\n case 'exec-policy':\n return result.needsOwnerApproval\n ? `ZHIN_NEEDS_OWNER:\\n${result.reason ?? '该命令需要 Owner 确认。'}`\n : `Error: ${result.reason ?? '该命令已被拒绝。'}`;\n default: {\n // role-gate / dangerous-tool-approval / sensitive-path:统一走 DangerousToolDecision 文案\n const decision: DangerousToolDecision = {\n allowed: result.allowed,\n reason: result.reason,\n needsOwnerApproval: result.needsOwnerApproval,\n role: result.role ?? 'unknown',\n };\n return result.needsOwnerApproval ? toOwnerSignal(decision) : toDenyError(decision);\n }\n }\n}\n"]}
@@ -62,6 +62,10 @@ export declare class UsageQuota {
62
62
  get enabled(): boolean;
63
63
  /** Checks a turn before it starts; an allowed group turn counts against the group's rate. */
64
64
  check(subject: UsageSubject): UsageQuotaDecision;
65
+ /** Preflight the whole request before consuming one rate slot per non-exempt group. */
66
+ checkBatch(subjects: readonly UsageSubject[]): UsageQuotaDecision & {
67
+ readonly deniedUserIds?: readonly string[];
68
+ };
65
69
  /** Adds a finished turn's token usage to the user's and the group's counters. */
66
70
  record(subject: UsageSubject, totalTokens: number): void;
67
71
  /** Drops expired counters. */
@@ -50,9 +50,29 @@ export class UsageQuota {
50
50
  }
51
51
  /** Checks a turn before it starts; an allowed group turn counts against the group's rate. */
52
52
  check(subject) {
53
+ return this.#check(subject, true, this.#now());
54
+ }
55
+ /** Preflight the whole request before consuming one rate slot per non-exempt group. */
56
+ checkBatch(subjects) {
57
+ const now = this.#now();
58
+ const denied = subjects.map(subject => ({ subject, decision: this.#check(subject, false, now) }))
59
+ .filter((entry) => !entry.decision.allowed);
60
+ const groupDenial = denied.find(entry => entry.decision.scope === 'group');
61
+ if (groupDenial)
62
+ return groupDenial.decision;
63
+ if (denied.length)
64
+ return { ...denied[0].decision, deniedUserIds: [...new Set(denied.map(entry => entry.subject.userId))] };
65
+ if (this.settings.perGroupRequestsPerMinute > 0) {
66
+ const groups = new Set(subjects.filter(subject => !(subject.owner && this.settings.exemptOwner))
67
+ .map(subject => subject.groupId).filter((id) => !!id));
68
+ for (const group of groups)
69
+ this.#add(`rate:group:${group}`, RATE_WINDOW_MS, now, 1);
70
+ }
71
+ return { allowed: true };
72
+ }
73
+ #check(subject, countRequest, now) {
53
74
  if (!this.enabled || (subject.owner && this.settings.exemptOwner))
54
75
  return { allowed: true };
55
- const now = this.#now();
56
76
  const { perUserTokens, perGroupTokens, perGroupRequestsPerMinute, windowMs } = this.settings;
57
77
  if (perUserTokens > 0) {
58
78
  const bucket = this.#current(`tokens:user:${subject.userId}`, windowMs, now);
@@ -72,7 +92,8 @@ export class UsageQuota {
72
92
  if (bucket.used >= perGroupRequestsPerMinute) {
73
93
  return deny('group', 'rate', bucket, RATE_WINDOW_MS, now, `本群消息太多啦,请约 ${formatWait(bucket, RATE_WINDOW_MS, now)}后再试。`);
74
94
  }
75
- this.#store.set(key, { windowStart: bucket.windowStart, used: bucket.used + 1 });
95
+ if (countRequest)
96
+ this.#store.set(key, { windowStart: bucket.windowStart, used: bucket.used + 1 });
76
97
  }
77
98
  return { allowed: true };
78
99
  }
@@ -1 +1 @@
1
- {"version":3,"file":"usage-quota.js","sourceRoot":"","sources":["../../src/security/usage-quota.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Per-user and per-group usage quotas (#727, cost / abuse control).\n *\n * Two independent limits, both off by default:\n * - token quota: total tokens a user, or a whole group, may spend within a\n * rolling fixed window (default 24 h);\n * - group rate: turns a single group may start per minute (the existing\n * `ai.agent.rateLimit` already limits each user).\n *\n * A user over their quota is told so and nobody else is affected; a group\n * over its quota stops only that group. The owner is exempt by default.\n */\n\nexport interface UsageQuotaSettings {\n /** Length of the quota window in ms (default 24 h). */\n readonly windowMs: number;\n /** Tokens one user may use per window across all chats; 0 = unlimited (default). */\n readonly perUserTokens: number;\n /** Tokens one group may use per window across all members; 0 = unlimited (default). */\n readonly perGroupTokens: number;\n /** Turns one group may start per minute; 0 = unlimited (default). */\n readonly perGroupRequestsPerMinute: number;\n /** The owner is never limited (default true). */\n readonly exemptOwner: boolean;\n}\n\nexport const DEFAULT_USAGE_QUOTA: UsageQuotaSettings = Object.freeze({\n windowMs: 24 * 60 * 60 * 1000,\n perUserTokens: 0,\n perGroupTokens: 0,\n perGroupRequestsPerMinute: 0,\n exemptOwner: true,\n});\n\nexport function resolveUsageQuota(raw?: Partial<UsageQuotaSettings>): UsageQuotaSettings {\n const count = (value: unknown, fallback: number) =>\n typeof value === 'number' && Number.isFinite(value) && value >= 0 ? Math.floor(value) : fallback;\n return Object.freeze({\n windowMs: Math.max(60_000, count(raw?.windowMs, DEFAULT_USAGE_QUOTA.windowMs)),\n perUserTokens: count(raw?.perUserTokens, 0),\n perGroupTokens: count(raw?.perGroupTokens, 0),\n perGroupRequestsPerMinute: count(raw?.perGroupRequestsPerMinute, 0),\n exemptOwner: raw?.exemptOwner !== false,\n });\n}\n\n/** One counter window. */\nexport interface UsageBucket {\n readonly windowStart: number;\n readonly used: number;\n}\n\n/** Storage for quota counters; the default keeps them in memory (reset on restart). */\nexport interface UsageQuotaStore {\n get(key: string): UsageBucket | undefined;\n set(key: string, bucket: UsageBucket): void;\n delete(key: string): void;\n keys(): Iterable<string>;\n}\n\nexport class InMemoryUsageQuotaStore implements UsageQuotaStore {\n readonly #buckets = new Map<string, UsageBucket>();\n get(key: string) { return this.#buckets.get(key); }\n set(key: string, bucket: UsageBucket) { this.#buckets.set(key, bucket); }\n delete(key: string) { this.#buckets.delete(key); }\n keys() { return this.#buckets.keys(); }\n}\n\n/** Defined in `@zhin.js/core` (#877) so plugins can build one with `usageSubjectFromMessage`. */\nimport type { UsageSubject } from '@zhin.js/core';\nexport type { UsageSubject };\n\nexport type UsageQuotaDecision =\n | Readonly<{ allowed: true }>\n | Readonly<{ allowed: false; scope: 'user' | 'group'; reason: 'tokens' | 'rate'; message: string; retryAfterSeconds: number }>;\n\nconst RATE_WINDOW_MS = 60_000;\n\nexport class UsageQuota {\n readonly settings: UsageQuotaSettings;\n readonly #store: UsageQuotaStore;\n readonly #now: () => number;\n\n constructor(settings?: Partial<UsageQuotaSettings>, store: UsageQuotaStore = new InMemoryUsageQuotaStore(), now: () => number = Date.now) {\n this.settings = resolveUsageQuota(settings);\n this.#store = store;\n this.#now = now;\n }\n\n get enabled(): boolean {\n const s = this.settings;\n return s.perUserTokens > 0 || s.perGroupTokens > 0 || s.perGroupRequestsPerMinute > 0;\n }\n\n /** Checks a turn before it starts; an allowed group turn counts against the group's rate. */\n check(subject: UsageSubject): UsageQuotaDecision {\n if (!this.enabled || (subject.owner && this.settings.exemptOwner)) return { allowed: true };\n const now = this.#now();\n const { perUserTokens, perGroupTokens, perGroupRequestsPerMinute, windowMs } = this.settings;\n if (perUserTokens > 0) {\n const bucket = this.#current(`tokens:user:${subject.userId}`, windowMs, now);\n if (bucket.used >= perUserTokens) {\n return deny('user', 'tokens', bucket, windowMs, now,\n `你在本周期内的 AI 用量已达上限(${perUserTokens} tokens),约 ${formatWait(bucket, windowMs, now)}后恢复。其他人不受影响。`);\n }\n }\n if (subject.groupId && perGroupTokens > 0) {\n const bucket = this.#current(`tokens:group:${subject.groupId}`, windowMs, now);\n if (bucket.used >= perGroupTokens) {\n return deny('group', 'tokens', bucket, windowMs, now,\n `本群在本周期内的 AI 用量已达上限,约 ${formatWait(bucket, windowMs, now)}后恢复。私聊和其他群不受影响。`);\n }\n }\n if (subject.groupId && perGroupRequestsPerMinute > 0) {\n const key = `rate:group:${subject.groupId}`;\n const bucket = this.#current(key, RATE_WINDOW_MS, now);\n if (bucket.used >= perGroupRequestsPerMinute) {\n return deny('group', 'rate', bucket, RATE_WINDOW_MS, now,\n `本群消息太多啦,请约 ${formatWait(bucket, RATE_WINDOW_MS, now)}后再试。`);\n }\n this.#store.set(key, { windowStart: bucket.windowStart, used: bucket.used + 1 });\n }\n return { allowed: true };\n }\n\n /** Adds a finished turn's token usage to the user's and the group's counters. */\n record(subject: UsageSubject, totalTokens: number): void {\n if (!this.enabled || totalTokens <= 0 || (subject.owner && this.settings.exemptOwner)) return;\n const now = this.#now();\n const { perUserTokens, perGroupTokens, windowMs } = this.settings;\n if (perUserTokens > 0) this.#add(`tokens:user:${subject.userId}`, windowMs, now, totalTokens);\n if (subject.groupId && perGroupTokens > 0) this.#add(`tokens:group:${subject.groupId}`, windowMs, now, totalTokens);\n }\n\n /** Drops expired counters. */\n prune(): void {\n const now = this.#now();\n for (const key of [...this.#store.keys()]) {\n const bucket = this.#store.get(key);\n const windowMs = key.startsWith('rate:') ? RATE_WINDOW_MS : this.settings.windowMs;\n if (!bucket || now - bucket.windowStart >= windowMs) this.#store.delete(key);\n }\n }\n\n #current(key: string, windowMs: number, now: number): UsageBucket {\n const bucket = this.#store.get(key);\n if (!bucket || now - bucket.windowStart >= windowMs) return { windowStart: now, used: 0 };\n return bucket;\n }\n\n #add(key: string, windowMs: number, now: number, amount: number): void {\n const bucket = this.#current(key, windowMs, now);\n this.#store.set(key, { windowStart: bucket.windowStart, used: bucket.used + amount });\n }\n}\n\nfunction deny(\n scope: 'user' | 'group',\n reason: 'tokens' | 'rate',\n bucket: UsageBucket,\n windowMs: number,\n now: number,\n message: string,\n): UsageQuotaDecision {\n const retryAfterSeconds = Math.max(1, Math.ceil((bucket.windowStart + windowMs - now) / 1000));\n return Object.freeze({ allowed: false, scope, reason, message, retryAfterSeconds });\n}\n\nfunction formatWait(bucket: UsageBucket, windowMs: number, now: number): string {\n const seconds = Math.max(1, Math.ceil((bucket.windowStart + windowMs - now) / 1000));\n if (seconds < 60) return `${seconds} 秒`;\n if (seconds < 3600) return `${Math.ceil(seconds / 60)} 分钟`;\n return `${Math.ceil(seconds / 3600)} 小时`;\n}\n"]}
1
+ {"version":3,"file":"usage-quota.js","sourceRoot":"","sources":["../../src/security/usage-quota.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Per-user and per-group usage quotas (#727, cost / abuse control).\n *\n * Two independent limits, both off by default:\n * - token quota: total tokens a user, or a whole group, may spend within a\n * rolling fixed window (default 24 h);\n * - group rate: turns a single group may start per minute (the existing\n * `ai.agent.rateLimit` already limits each user).\n *\n * A user over their quota is told so and nobody else is affected; a group\n * over its quota stops only that group. The owner is exempt by default.\n */\n\nexport interface UsageQuotaSettings {\n /** Length of the quota window in ms (default 24 h). */\n readonly windowMs: number;\n /** Tokens one user may use per window across all chats; 0 = unlimited (default). */\n readonly perUserTokens: number;\n /** Tokens one group may use per window across all members; 0 = unlimited (default). */\n readonly perGroupTokens: number;\n /** Turns one group may start per minute; 0 = unlimited (default). */\n readonly perGroupRequestsPerMinute: number;\n /** The owner is never limited (default true). */\n readonly exemptOwner: boolean;\n}\n\nexport const DEFAULT_USAGE_QUOTA: UsageQuotaSettings = Object.freeze({\n windowMs: 24 * 60 * 60 * 1000,\n perUserTokens: 0,\n perGroupTokens: 0,\n perGroupRequestsPerMinute: 0,\n exemptOwner: true,\n});\n\nexport function resolveUsageQuota(raw?: Partial<UsageQuotaSettings>): UsageQuotaSettings {\n const count = (value: unknown, fallback: number) =>\n typeof value === 'number' && Number.isFinite(value) && value >= 0 ? Math.floor(value) : fallback;\n return Object.freeze({\n windowMs: Math.max(60_000, count(raw?.windowMs, DEFAULT_USAGE_QUOTA.windowMs)),\n perUserTokens: count(raw?.perUserTokens, 0),\n perGroupTokens: count(raw?.perGroupTokens, 0),\n perGroupRequestsPerMinute: count(raw?.perGroupRequestsPerMinute, 0),\n exemptOwner: raw?.exemptOwner !== false,\n });\n}\n\n/** One counter window. */\nexport interface UsageBucket {\n readonly windowStart: number;\n readonly used: number;\n}\n\n/** Storage for quota counters; the default keeps them in memory (reset on restart). */\nexport interface UsageQuotaStore {\n get(key: string): UsageBucket | undefined;\n set(key: string, bucket: UsageBucket): void;\n delete(key: string): void;\n keys(): Iterable<string>;\n}\n\nexport class InMemoryUsageQuotaStore implements UsageQuotaStore {\n readonly #buckets = new Map<string, UsageBucket>();\n get(key: string) { return this.#buckets.get(key); }\n set(key: string, bucket: UsageBucket) { this.#buckets.set(key, bucket); }\n delete(key: string) { this.#buckets.delete(key); }\n keys() { return this.#buckets.keys(); }\n}\n\n/** Defined in `@zhin.js/core` (#877) so plugins can build one with `usageSubjectFromMessage`. */\nimport type { UsageSubject } from '@zhin.js/core';\nexport type { UsageSubject };\n\nexport type UsageQuotaDecision =\n | Readonly<{ allowed: true }>\n | Readonly<{ allowed: false; scope: 'user' | 'group'; reason: 'tokens' | 'rate'; message: string; retryAfterSeconds: number }>;\n\nconst RATE_WINDOW_MS = 60_000;\n\nexport class UsageQuota {\n readonly settings: UsageQuotaSettings;\n readonly #store: UsageQuotaStore;\n readonly #now: () => number;\n\n constructor(settings?: Partial<UsageQuotaSettings>, store: UsageQuotaStore = new InMemoryUsageQuotaStore(), now: () => number = Date.now) {\n this.settings = resolveUsageQuota(settings);\n this.#store = store;\n this.#now = now;\n }\n\n get enabled(): boolean {\n const s = this.settings;\n return s.perUserTokens > 0 || s.perGroupTokens > 0 || s.perGroupRequestsPerMinute > 0;\n }\n\n /** Checks a turn before it starts; an allowed group turn counts against the group's rate. */\n check(subject: UsageSubject): UsageQuotaDecision {\n return this.#check(subject, true, this.#now());\n }\n\n /** Preflight the whole request before consuming one rate slot per non-exempt group. */\n checkBatch(subjects: readonly UsageSubject[]): UsageQuotaDecision & { readonly deniedUserIds?: readonly string[] } {\n const now = this.#now();\n const denied = subjects.map(subject => ({ subject, decision: this.#check(subject, false, now) }))\n .filter((entry): entry is { subject: UsageSubject; decision: Extract<UsageQuotaDecision, { allowed: false }> } => !entry.decision.allowed);\n const groupDenial = denied.find(entry => entry.decision.scope === 'group');\n if (groupDenial) return groupDenial.decision;\n if (denied.length) return { ...denied[0]!.decision, deniedUserIds: [...new Set(denied.map(entry => entry.subject.userId))] };\n if (this.settings.perGroupRequestsPerMinute > 0) {\n const groups = new Set(subjects.filter(subject => !(subject.owner && this.settings.exemptOwner))\n .map(subject => subject.groupId).filter((id): id is string => !!id));\n for (const group of groups) this.#add(`rate:group:${group}`, RATE_WINDOW_MS, now, 1);\n }\n return { allowed: true };\n }\n\n #check(subject: UsageSubject, countRequest: boolean, now: number): UsageQuotaDecision {\n if (!this.enabled || (subject.owner && this.settings.exemptOwner)) return { allowed: true };\n const { perUserTokens, perGroupTokens, perGroupRequestsPerMinute, windowMs } = this.settings;\n if (perUserTokens > 0) {\n const bucket = this.#current(`tokens:user:${subject.userId}`, windowMs, now);\n if (bucket.used >= perUserTokens) {\n return deny('user', 'tokens', bucket, windowMs, now,\n `你在本周期内的 AI 用量已达上限(${perUserTokens} tokens),约 ${formatWait(bucket, windowMs, now)}后恢复。其他人不受影响。`);\n }\n }\n if (subject.groupId && perGroupTokens > 0) {\n const bucket = this.#current(`tokens:group:${subject.groupId}`, windowMs, now);\n if (bucket.used >= perGroupTokens) {\n return deny('group', 'tokens', bucket, windowMs, now,\n `本群在本周期内的 AI 用量已达上限,约 ${formatWait(bucket, windowMs, now)}后恢复。私聊和其他群不受影响。`);\n }\n }\n if (subject.groupId && perGroupRequestsPerMinute > 0) {\n const key = `rate:group:${subject.groupId}`;\n const bucket = this.#current(key, RATE_WINDOW_MS, now);\n if (bucket.used >= perGroupRequestsPerMinute) {\n return deny('group', 'rate', bucket, RATE_WINDOW_MS, now,\n `本群消息太多啦,请约 ${formatWait(bucket, RATE_WINDOW_MS, now)}后再试。`);\n }\n if (countRequest) this.#store.set(key, { windowStart: bucket.windowStart, used: bucket.used + 1 });\n }\n return { allowed: true };\n }\n\n /** Adds a finished turn's token usage to the user's and the group's counters. */\n record(subject: UsageSubject, totalTokens: number): void {\n if (!this.enabled || totalTokens <= 0 || (subject.owner && this.settings.exemptOwner)) return;\n const now = this.#now();\n const { perUserTokens, perGroupTokens, windowMs } = this.settings;\n if (perUserTokens > 0) this.#add(`tokens:user:${subject.userId}`, windowMs, now, totalTokens);\n if (subject.groupId && perGroupTokens > 0) this.#add(`tokens:group:${subject.groupId}`, windowMs, now, totalTokens);\n }\n\n /** Drops expired counters. */\n prune(): void {\n const now = this.#now();\n for (const key of [...this.#store.keys()]) {\n const bucket = this.#store.get(key);\n const windowMs = key.startsWith('rate:') ? RATE_WINDOW_MS : this.settings.windowMs;\n if (!bucket || now - bucket.windowStart >= windowMs) this.#store.delete(key);\n }\n }\n\n #current(key: string, windowMs: number, now: number): UsageBucket {\n const bucket = this.#store.get(key);\n if (!bucket || now - bucket.windowStart >= windowMs) return { windowStart: now, used: 0 };\n return bucket;\n }\n\n #add(key: string, windowMs: number, now: number, amount: number): void {\n const bucket = this.#current(key, windowMs, now);\n this.#store.set(key, { windowStart: bucket.windowStart, used: bucket.used + amount });\n }\n}\n\nfunction deny(\n scope: 'user' | 'group',\n reason: 'tokens' | 'rate',\n bucket: UsageBucket,\n windowMs: number,\n now: number,\n message: string,\n): UsageQuotaDecision {\n const retryAfterSeconds = Math.max(1, Math.ceil((bucket.windowStart + windowMs - now) / 1000));\n return Object.freeze({ allowed: false, scope, reason, message, retryAfterSeconds });\n}\n\nfunction formatWait(bucket: UsageBucket, windowMs: number, now: number): string {\n const seconds = Math.max(1, Math.ceil((bucket.windowStart + windowMs - now) / 1000));\n if (seconds < 60) return `${seconds} 秒`;\n if (seconds < 3600) return `${Math.ceil(seconds / 60)} 分钟`;\n return `${Math.ceil(seconds / 3600)} 小时`;\n}\n"]}
@@ -1,5 +1,7 @@
1
1
  /** Blocking human-in-the-loop security authority. */
2
2
  export interface ApprovalRequestInput {
3
+ /** Fresh human consent: no model reviewer, master shortcut, or remembered grant. */
4
+ humanOnly?: boolean;
3
5
  requestId: string;
4
6
  /** Canonical session boundary for approval memory. */
5
7
  sessionKey?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"approval-port.js","sourceRoot":"","sources":["../../src/session/approval-port.ts"],"names":[],"mappings":"AAwDA,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,IAAkB,EAClB,KAA2B;IAE3B,MAAM,WAAW,GAAG,IAA2D,CAAC;IAChF,IAAI,OAAO,WAAW,CAAC,sBAAsB,KAAK,UAAU,EAAE,CAAC;QAC7D,OAAO,WAAW,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,OAAO,WAAW,CAAC,uBAAuB,KAAK,UAAU,EAAE,CAAC;QAC9D,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC;QAClE,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IACrG,CAAC;IACD,OAAO,MAAM,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;QACtC,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE;QAClD,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAAwB;IAChE,OAAO,OAAO,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;AACrE,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,oBAAoB,CAAC,OAAyD;IAC5F,QAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;QACvB,KAAK,QAAQ;YACX,IAAI,OAAO,CAAC,EAAE,KAAK,UAAU,EAAE,CAAC;gBAC9B,OAAO,8EAA8E;sBACjF,oGAAoG,CAAC;YAC3G,CAAC;YACD,OAAO,OAAO,CAAC,EAAE,KAAK,YAAY;gBAChC,CAAC,CAAC,2GAA2G;gBAC7G,CAAC,CAAC,oGAAoG,CAAC;QAC3G,KAAK,WAAW;YACd,OAAO,4EAA4E;kBAC/E,qFAAqF,CAAC;QAC5F,KAAK,aAAa;YAChB,OAAO,6DAA6D,OAAO,CAAC,MAAM,KAAK;kBACnF,uCAAuC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,IAA8B;IACpE,OAAO,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC;AACnD,CAAC","sourcesContent":["/** Blocking human-in-the-loop security authority. */\nexport interface ApprovalRequestInput {\n requestId: string;\n /** Canonical session boundary for approval memory. */\n sessionKey?: string;\n /** Conversation shape controls which human approval scopes are meaningful. */\n conversationScope?: 'private' | 'group' | 'channel';\n /** Principal whose proposed operation is being reviewed. */\n requesterId?: string;\n toolName: string;\n /** Stable fingerprint for the concrete operation covered by a remembered grant. */\n scopeKey?: string;\n /** Allow the approval surface to offer a Host-lifetime session grant. */\n remember?: 'session';\n question: string;\n timeoutMs?: number;\n /** The adapter must settle promptly and deny when the owning Turn is cancelled. */\n signal: AbortSignal;\n}\n\nexport type ApprovalDecision =\n | 'reject'\n | 'approve-once'\n | 'approve-session'\n | 'approve-always';\n\nexport interface ApprovalDecisionMemory {\n recall(input: ApprovalRequestInput): boolean | undefined;\n remember(input: ApprovalRequestInput, decision: ApprovalDecision): void;\n}\n\nexport interface ApprovalPort {\n /** False means this transport cannot make an approval decision for this turn. */\n readonly available?: boolean;\n requestApproval(input: ApprovalRequestInput): Promise<boolean>;\n resolveApproval?(requestId: string, approved: boolean): boolean;\n}\n\nexport interface ApprovalDecisionPort extends ApprovalPort {\n requestApprovalDecision(input: ApprovalRequestInput): Promise<ApprovalDecision>;\n}\n\n/**\n * Why an approval request settled the way it did. A boolean cannot tell the\n * model whether a human said no, nobody answered, or nobody could be asked.\n */\nexport type ApprovalOutcome =\n | Readonly<{ status: 'approved'; decision: Exclude<ApprovalDecision, 'reject'> }>\n | Readonly<{ status: 'denied'; by: 'user' | 'reviewer' | 'remembered' }>\n | Readonly<{ status: 'timed_out' }>\n | Readonly<{ status: 'unavailable'; reason: string }>;\n\nexport interface ApprovalOutcomePort extends ApprovalPort {\n requestApprovalOutcome(input: ApprovalRequestInput): Promise<ApprovalOutcome>;\n}\n\n/** Ask any port for a typed outcome; legacy boolean ports report a user decision. */\nexport async function requestApprovalOutcome(\n port: ApprovalPort,\n input: ApprovalRequestInput,\n): Promise<ApprovalOutcome> {\n const outcomePort = port as Partial<ApprovalOutcomePort & ApprovalDecisionPort>;\n if (typeof outcomePort.requestApprovalOutcome === 'function') {\n return outcomePort.requestApprovalOutcome(input);\n }\n if (typeof outcomePort.requestApprovalDecision === 'function') {\n const decision = await outcomePort.requestApprovalDecision(input);\n return decision === 'reject' ? { status: 'denied', by: 'user' } : { status: 'approved', decision };\n }\n return await port.requestApproval(input)\n ? { status: 'approved', decision: 'approve-once' }\n : { status: 'denied', by: 'user' };\n}\n\nexport function approvalOutcomeToDecision(outcome: ApprovalOutcome): ApprovalDecision {\n return outcome.status === 'approved' ? outcome.decision : 'reject';\n}\n\n/** Tool-result text for a non-approved outcome; phrased so the model does not blindly retry. */\nexport function formatApprovalDenial(outcome: Exclude<ApprovalOutcome, { status: 'approved' }>): string {\n switch (outcome.status) {\n case 'denied':\n if (outcome.by === 'reviewer') {\n return 'Tool call rejected by the automatic approval reviewer (no human was asked). '\n + 'Do not retry the same call; continue without it or tell the user it needs their explicit approval.';\n }\n return outcome.by === 'remembered'\n ? 'Tool call denied: the user already rejected this operation earlier in this conversation. Do not retry it.'\n : 'Tool call denied by the user. Do not retry it; continue without it or ask the user how to proceed.';\n case 'timed_out':\n return 'Tool call not approved: the approval request timed out without an answer. '\n + 'Do not retry automatically; tell the user the action is waiting for their approval.';\n case 'unavailable':\n return `Tool call not approved: no approval channel is available (${outcome.reason}). `\n + 'Do not retry it in this conversation.';\n }\n}\n\nexport function isApprovalPortAvailable(port: ApprovalPort | undefined): port is ApprovalPort {\n return Boolean(port && port.available !== false);\n}\n"]}
1
+ {"version":3,"file":"approval-port.js","sourceRoot":"","sources":["../../src/session/approval-port.ts"],"names":[],"mappings":"AA0DA,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,IAAkB,EAClB,KAA2B;IAE3B,MAAM,WAAW,GAAG,IAA2D,CAAC;IAChF,IAAI,OAAO,WAAW,CAAC,sBAAsB,KAAK,UAAU,EAAE,CAAC;QAC7D,OAAO,WAAW,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,OAAO,WAAW,CAAC,uBAAuB,KAAK,UAAU,EAAE,CAAC;QAC9D,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC;QAClE,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IACrG,CAAC;IACD,OAAO,MAAM,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;QACtC,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE;QAClD,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAAwB;IAChE,OAAO,OAAO,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;AACrE,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,oBAAoB,CAAC,OAAyD;IAC5F,QAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;QACvB,KAAK,QAAQ;YACX,IAAI,OAAO,CAAC,EAAE,KAAK,UAAU,EAAE,CAAC;gBAC9B,OAAO,8EAA8E;sBACjF,oGAAoG,CAAC;YAC3G,CAAC;YACD,OAAO,OAAO,CAAC,EAAE,KAAK,YAAY;gBAChC,CAAC,CAAC,2GAA2G;gBAC7G,CAAC,CAAC,oGAAoG,CAAC;QAC3G,KAAK,WAAW;YACd,OAAO,4EAA4E;kBAC/E,qFAAqF,CAAC;QAC5F,KAAK,aAAa;YAChB,OAAO,6DAA6D,OAAO,CAAC,MAAM,KAAK;kBACnF,uCAAuC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,IAA8B;IACpE,OAAO,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC;AACnD,CAAC","sourcesContent":["/** Blocking human-in-the-loop security authority. */\nexport interface ApprovalRequestInput {\n /** Fresh human consent: no model reviewer, master shortcut, or remembered grant. */\n humanOnly?: boolean;\n requestId: string;\n /** Canonical session boundary for approval memory. */\n sessionKey?: string;\n /** Conversation shape controls which human approval scopes are meaningful. */\n conversationScope?: 'private' | 'group' | 'channel';\n /** Principal whose proposed operation is being reviewed. */\n requesterId?: string;\n toolName: string;\n /** Stable fingerprint for the concrete operation covered by a remembered grant. */\n scopeKey?: string;\n /** Allow the approval surface to offer a Host-lifetime session grant. */\n remember?: 'session';\n question: string;\n timeoutMs?: number;\n /** The adapter must settle promptly and deny when the owning Turn is cancelled. */\n signal: AbortSignal;\n}\n\nexport type ApprovalDecision =\n | 'reject'\n | 'approve-once'\n | 'approve-session'\n | 'approve-always';\n\nexport interface ApprovalDecisionMemory {\n recall(input: ApprovalRequestInput): boolean | undefined;\n remember(input: ApprovalRequestInput, decision: ApprovalDecision): void;\n}\n\nexport interface ApprovalPort {\n /** False means this transport cannot make an approval decision for this turn. */\n readonly available?: boolean;\n requestApproval(input: ApprovalRequestInput): Promise<boolean>;\n resolveApproval?(requestId: string, approved: boolean): boolean;\n}\n\nexport interface ApprovalDecisionPort extends ApprovalPort {\n requestApprovalDecision(input: ApprovalRequestInput): Promise<ApprovalDecision>;\n}\n\n/**\n * Why an approval request settled the way it did. A boolean cannot tell the\n * model whether a human said no, nobody answered, or nobody could be asked.\n */\nexport type ApprovalOutcome =\n | Readonly<{ status: 'approved'; decision: Exclude<ApprovalDecision, 'reject'> }>\n | Readonly<{ status: 'denied'; by: 'user' | 'reviewer' | 'remembered' }>\n | Readonly<{ status: 'timed_out' }>\n | Readonly<{ status: 'unavailable'; reason: string }>;\n\nexport interface ApprovalOutcomePort extends ApprovalPort {\n requestApprovalOutcome(input: ApprovalRequestInput): Promise<ApprovalOutcome>;\n}\n\n/** Ask any port for a typed outcome; legacy boolean ports report a user decision. */\nexport async function requestApprovalOutcome(\n port: ApprovalPort,\n input: ApprovalRequestInput,\n): Promise<ApprovalOutcome> {\n const outcomePort = port as Partial<ApprovalOutcomePort & ApprovalDecisionPort>;\n if (typeof outcomePort.requestApprovalOutcome === 'function') {\n return outcomePort.requestApprovalOutcome(input);\n }\n if (typeof outcomePort.requestApprovalDecision === 'function') {\n const decision = await outcomePort.requestApprovalDecision(input);\n return decision === 'reject' ? { status: 'denied', by: 'user' } : { status: 'approved', decision };\n }\n return await port.requestApproval(input)\n ? { status: 'approved', decision: 'approve-once' }\n : { status: 'denied', by: 'user' };\n}\n\nexport function approvalOutcomeToDecision(outcome: ApprovalOutcome): ApprovalDecision {\n return outcome.status === 'approved' ? outcome.decision : 'reject';\n}\n\n/** Tool-result text for a non-approved outcome; phrased so the model does not blindly retry. */\nexport function formatApprovalDenial(outcome: Exclude<ApprovalOutcome, { status: 'approved' }>): string {\n switch (outcome.status) {\n case 'denied':\n if (outcome.by === 'reviewer') {\n return 'Tool call rejected by the automatic approval reviewer (no human was asked). '\n + 'Do not retry the same call; continue without it or tell the user it needs their explicit approval.';\n }\n return outcome.by === 'remembered'\n ? 'Tool call denied: the user already rejected this operation earlier in this conversation. Do not retry it.'\n : 'Tool call denied by the user. Do not retry it; continue without it or ask the user how to proceed.';\n case 'timed_out':\n return 'Tool call not approved: the approval request timed out without an answer. '\n + 'Do not retry automatically; tell the user the action is waiting for their approval.';\n case 'unavailable':\n return `Tool call not approved: no approval channel is available (${outcome.reason}). `\n + 'Do not retry it in this conversation.';\n }\n}\n\nexport function isApprovalPortAvailable(port: ApprovalPort | undefined): port is ApprovalPort {\n return Boolean(port && port.available !== false);\n}\n"]}
@@ -14,4 +14,4 @@ export declare class ApprovalReviewAgent {
14
14
  remember(input: ApprovalRequestInput, decision: ApprovalDecision, source?: ApprovalHistoryEntry['source']): void;
15
15
  }
16
16
  export declare function createAutoApprovalPort(reviewer: ApprovalReviewAgent, ask?: ApprovalPort, judgment?: ApprovalJudgmentPort): ApprovalOutcomePort;
17
- export declare function createBypassApprovalPort(): ApprovalPort;
17
+ export declare function createBypassApprovalPort(ask?: ApprovalPort): ApprovalOutcomePort;
@@ -85,6 +85,11 @@ export function createAutoApprovalPort(reviewer, ask, judgment) {
85
85
  async requestApprovalOutcome(input) {
86
86
  try {
87
87
  input.signal.throwIfAborted();
88
+ if (input.humanOnly) {
89
+ return isApprovalPortAvailable(ask)
90
+ ? await settleWithinTimeout(requestApprovalOutcome(ask, input), input)
91
+ : { status: 'unavailable', reason: 'this tool requires fresh human approval' };
92
+ }
88
93
  const recalled = reviewer.recall(input);
89
94
  if (recalled !== undefined) {
90
95
  return recalled
@@ -122,11 +127,21 @@ export function createAutoApprovalPort(reviewer, ask, judgment) {
122
127
  };
123
128
  return Object.freeze(port);
124
129
  }
125
- export function createBypassApprovalPort() {
126
- return Object.freeze({
130
+ export function createBypassApprovalPort(ask) {
131
+ const port = {
127
132
  available: true,
128
- requestApproval: async () => true,
129
- });
133
+ async requestApprovalOutcome(input) {
134
+ if (input.signal.aborted)
135
+ return { status: 'unavailable', reason: 'approval request cancelled' };
136
+ if (input.humanOnly)
137
+ return isApprovalPortAvailable(ask)
138
+ ? requestApprovalOutcome(ask, input)
139
+ : { status: 'unavailable', reason: 'this tool requires fresh human approval' };
140
+ return { status: 'approved', decision: 'approve-once' };
141
+ },
142
+ requestApproval: async (input) => (await port.requestApprovalOutcome(input)).status === 'approved',
143
+ };
144
+ return Object.freeze(port);
130
145
  }
131
146
  async function settleBeforeAbort(result, signal, cancelled) {
132
147
  return await new Promise((resolve) => {
@@ -1 +1 @@
1
- {"version":3,"file":"approval-review-agent.js","sourceRoot":"","sources":["../../src/session/approval-review-agent.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ApprovalHistoryEntry,\n ApprovalJudgmentPort,\n ApprovalReviewDecision,\n} from '../decision/approval-judgment.js';\nimport {\n approvalOutcomeToDecision,\n isApprovalPortAvailable,\n requestApprovalOutcome,\n type ApprovalDecision,\n type ApprovalOutcome,\n type ApprovalOutcomePort,\n type ApprovalPort,\n type ApprovalRequestInput,\n} from './approval-port.js';\n\nexport type { ApprovalReviewDecision } from '../decision/approval-judgment.js';\n\nexport interface ApprovalReviewAgentOptions {\n readonly judgment: ApprovalJudgmentPort;\n readonly timeoutMs?: number;\n}\n\n/** Tool-less, fail-closed reviewer used by approvalMode=auto. */\nexport class ApprovalReviewAgent {\n readonly #judgment: ApprovalJudgmentPort;\n readonly #timeoutMs: number;\n readonly #memory: ApprovalHistoryEntry[] = [];\n\n constructor(options: ApprovalReviewAgentOptions) {\n this.#judgment = options.judgment;\n this.#timeoutMs = options.timeoutMs ?? 30_000;\n }\n\n async review(\n input: ApprovalRequestInput,\n judgment: ApprovalJudgmentPort = this.#judgment,\n ): Promise<ApprovalReviewDecision> {\n const timeoutMs = Math.min(input.timeoutMs ?? this.#timeoutMs, this.#timeoutMs);\n const controller = new AbortController();\n const abort = () => controller.abort(input.signal.reason);\n const timer = setTimeout(() => controller.abort(new Error('Approval review timed out')), timeoutMs);\n input.signal.addEventListener('abort', abort, { once: true });\n try {\n input.signal.throwIfAborted();\n const result = await settleBeforeAbort(\n judgment.judge({ request: input, priorDecisions: this.#history(input), signal: controller.signal }),\n controller.signal,\n { decision: 'reject' as const, reason: 'Approval review cancelled' },\n );\n return !controller.signal.aborted && (result.decision === 'approve' || result.decision === 'ask')\n ? result.decision\n : 'reject';\n } catch {\n return 'reject';\n } finally {\n clearTimeout(timer);\n input.signal.removeEventListener('abort', abort);\n }\n }\n\n recall(input: ApprovalRequestInput): boolean | undefined {\n if (!input.scopeKey) return undefined;\n let match: ApprovalHistoryEntry | undefined;\n for (let index = this.#memory.length - 1; index >= 0; index -= 1) {\n const entry = this.#memory[index];\n if (entry?.toolName === input.toolName\n && entry.scopeKey === input.scopeKey\n && this.#covers(entry, input)) {\n match = entry;\n break;\n }\n }\n if (!match) return undefined;\n return match.decision !== 'reject';\n }\n\n remember(\n input: ApprovalRequestInput,\n decision: ApprovalDecision,\n source: ApprovalHistoryEntry['source'] = 'human',\n ): void {\n if (decision !== 'approve-once'\n && (!input.sessionKey || !input.requesterId || !input.scopeKey)) return;\n this.#memory.push(Object.freeze({\n sessionKey: input.sessionKey,\n requesterId: input.requesterId,\n toolName: input.toolName,\n scopeKey: input.scopeKey,\n question: input.question,\n decision,\n source,\n }));\n if (this.#memory.length > 256) this.#memory.splice(0, this.#memory.length - 256);\n }\n\n #history(input: ApprovalRequestInput): readonly ApprovalHistoryEntry[] {\n return this.#memory.filter(entry =>\n (input.sessionKey != null && entry.sessionKey === input.sessionKey)\n ).slice(-12);\n }\n\n #covers(entry: ApprovalHistoryEntry, input: ApprovalRequestInput): boolean {\n if (entry.decision === 'approve-session') {\n return entry.sessionKey != null && entry.sessionKey === input.sessionKey;\n }\n if (entry.decision === 'approve-always') {\n return entry.sessionKey === input.sessionKey && entry.requesterId === input.requesterId;\n }\n if (entry.decision === 'reject') {\n return entry.source === 'human'\n && entry.sessionKey === input.sessionKey && entry.requesterId === input.requesterId;\n }\n return false;\n }\n}\n\nexport function createAutoApprovalPort(\n reviewer: ApprovalReviewAgent,\n ask?: ApprovalPort,\n judgment?: ApprovalJudgmentPort,\n): ApprovalOutcomePort {\n const port: ApprovalOutcomePort = {\n available: true,\n async requestApprovalOutcome(input: ApprovalRequestInput): Promise<ApprovalOutcome> {\n try {\n input.signal.throwIfAborted();\n const recalled = reviewer.recall(input);\n if (recalled !== undefined) {\n return recalled\n ? { status: 'approved', decision: 'approve-once' }\n : { status: 'denied', by: 'remembered' };\n }\n // The reviewer bounds its own judgment; a human escalation is bounded only\n // by an explicit request timeout, otherwise by the approval surface itself.\n const decision = await reviewer.review(input, judgment);\n if (decision === 'approve') {\n reviewer.remember(input, 'approve-once', 'reviewer');\n return { status: 'approved', decision: 'approve-once' };\n }\n if (decision === 'ask') {\n if (!isApprovalPortAvailable(ask)) {\n return { status: 'unavailable', reason: 'the reviewer escalated to a human, but nobody can be asked here' };\n }\n const outcome = await settleWithinTimeout(requestApprovalOutcome(ask, input), input);\n // Only an explicit human answer is remembered; a timeout is not a \"no\".\n if (outcome.status === 'approved' || (outcome.status === 'denied' && outcome.by === 'user')) {\n reviewer.remember(input, approvalOutcomeToDecision(outcome), 'human');\n }\n return outcome;\n }\n reviewer.remember(input, 'reject', 'reviewer');\n return { status: 'denied', by: 'reviewer' };\n } catch {\n return { status: 'unavailable', reason: 'approval review failed' };\n }\n },\n async requestApproval(input: ApprovalRequestInput) {\n return (await port.requestApprovalOutcome(input)).status === 'approved';\n },\n };\n return Object.freeze(port);\n}\n\nexport function createBypassApprovalPort(): ApprovalPort {\n return Object.freeze({\n available: true,\n requestApproval: async () => true,\n });\n}\n\n\nasync function settleBeforeAbort<T>(\n result: Promise<T>,\n signal: AbortSignal,\n cancelled: T,\n): Promise<T> {\n return await new Promise<T>((resolve) => {\n let settled = false;\n const finish = (value: T) => {\n if (settled) return;\n settled = true;\n signal.removeEventListener('abort', deny);\n resolve(value);\n };\n const deny = () => finish(cancelled);\n signal.addEventListener('abort', deny, { once: true });\n // Always attach rejection handling, including when cancellation won the race.\n void result.then(value => finish(value), () => finish(cancelled));\n if (signal.aborted) deny();\n });\n}\n\n/** Bound a human escalation by the caller's explicit timeout; cancellation stays fail-closed. */\nasync function settleWithinTimeout(\n result: Promise<ApprovalOutcome>,\n input: ApprovalRequestInput,\n): Promise<ApprovalOutcome> {\n const cancelled: ApprovalOutcome = { status: 'unavailable', reason: 'the turn was cancelled' };\n const timedOut: ApprovalOutcome = { status: 'timed_out' };\n return await new Promise<ApprovalOutcome>((resolve) => {\n let settled = false;\n const timer = input.timeoutMs === undefined\n ? undefined\n : setTimeout(() => finish(timedOut), input.timeoutMs);\n const onAbort = () => finish(cancelled);\n function finish(value: ApprovalOutcome) {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n input.signal.removeEventListener('abort', onAbort);\n resolve(value);\n }\n input.signal.addEventListener('abort', onAbort, { once: true });\n void result.then(finish, () => finish({ status: 'unavailable', reason: 'the approval prompt failed' }));\n if (input.signal.aborted) onAbort();\n });\n}\n"]}
1
+ {"version":3,"file":"approval-review-agent.js","sourceRoot":"","sources":["../../src/session/approval-review-agent.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ApprovalHistoryEntry,\n ApprovalJudgmentPort,\n ApprovalReviewDecision,\n} from '../decision/approval-judgment.js';\nimport {\n approvalOutcomeToDecision,\n isApprovalPortAvailable,\n requestApprovalOutcome,\n type ApprovalDecision,\n type ApprovalOutcome,\n type ApprovalOutcomePort,\n type ApprovalPort,\n type ApprovalRequestInput,\n} from './approval-port.js';\n\nexport type { ApprovalReviewDecision } from '../decision/approval-judgment.js';\n\nexport interface ApprovalReviewAgentOptions {\n readonly judgment: ApprovalJudgmentPort;\n readonly timeoutMs?: number;\n}\n\n/** Tool-less, fail-closed reviewer used by approvalMode=auto. */\nexport class ApprovalReviewAgent {\n readonly #judgment: ApprovalJudgmentPort;\n readonly #timeoutMs: number;\n readonly #memory: ApprovalHistoryEntry[] = [];\n\n constructor(options: ApprovalReviewAgentOptions) {\n this.#judgment = options.judgment;\n this.#timeoutMs = options.timeoutMs ?? 30_000;\n }\n\n async review(\n input: ApprovalRequestInput,\n judgment: ApprovalJudgmentPort = this.#judgment,\n ): Promise<ApprovalReviewDecision> {\n const timeoutMs = Math.min(input.timeoutMs ?? this.#timeoutMs, this.#timeoutMs);\n const controller = new AbortController();\n const abort = () => controller.abort(input.signal.reason);\n const timer = setTimeout(() => controller.abort(new Error('Approval review timed out')), timeoutMs);\n input.signal.addEventListener('abort', abort, { once: true });\n try {\n input.signal.throwIfAborted();\n const result = await settleBeforeAbort(\n judgment.judge({ request: input, priorDecisions: this.#history(input), signal: controller.signal }),\n controller.signal,\n { decision: 'reject' as const, reason: 'Approval review cancelled' },\n );\n return !controller.signal.aborted && (result.decision === 'approve' || result.decision === 'ask')\n ? result.decision\n : 'reject';\n } catch {\n return 'reject';\n } finally {\n clearTimeout(timer);\n input.signal.removeEventListener('abort', abort);\n }\n }\n\n recall(input: ApprovalRequestInput): boolean | undefined {\n if (!input.scopeKey) return undefined;\n let match: ApprovalHistoryEntry | undefined;\n for (let index = this.#memory.length - 1; index >= 0; index -= 1) {\n const entry = this.#memory[index];\n if (entry?.toolName === input.toolName\n && entry.scopeKey === input.scopeKey\n && this.#covers(entry, input)) {\n match = entry;\n break;\n }\n }\n if (!match) return undefined;\n return match.decision !== 'reject';\n }\n\n remember(\n input: ApprovalRequestInput,\n decision: ApprovalDecision,\n source: ApprovalHistoryEntry['source'] = 'human',\n ): void {\n if (decision !== 'approve-once'\n && (!input.sessionKey || !input.requesterId || !input.scopeKey)) return;\n this.#memory.push(Object.freeze({\n sessionKey: input.sessionKey,\n requesterId: input.requesterId,\n toolName: input.toolName,\n scopeKey: input.scopeKey,\n question: input.question,\n decision,\n source,\n }));\n if (this.#memory.length > 256) this.#memory.splice(0, this.#memory.length - 256);\n }\n\n #history(input: ApprovalRequestInput): readonly ApprovalHistoryEntry[] {\n return this.#memory.filter(entry =>\n (input.sessionKey != null && entry.sessionKey === input.sessionKey)\n ).slice(-12);\n }\n\n #covers(entry: ApprovalHistoryEntry, input: ApprovalRequestInput): boolean {\n if (entry.decision === 'approve-session') {\n return entry.sessionKey != null && entry.sessionKey === input.sessionKey;\n }\n if (entry.decision === 'approve-always') {\n return entry.sessionKey === input.sessionKey && entry.requesterId === input.requesterId;\n }\n if (entry.decision === 'reject') {\n return entry.source === 'human'\n && entry.sessionKey === input.sessionKey && entry.requesterId === input.requesterId;\n }\n return false;\n }\n}\n\nexport function createAutoApprovalPort(\n reviewer: ApprovalReviewAgent,\n ask?: ApprovalPort,\n judgment?: ApprovalJudgmentPort,\n): ApprovalOutcomePort {\n const port: ApprovalOutcomePort = {\n available: true,\n async requestApprovalOutcome(input: ApprovalRequestInput): Promise<ApprovalOutcome> {\n try {\n input.signal.throwIfAborted();\n if (input.humanOnly) {\n return isApprovalPortAvailable(ask)\n ? await settleWithinTimeout(requestApprovalOutcome(ask, input), input)\n : { status: 'unavailable', reason: 'this tool requires fresh human approval' };\n }\n const recalled = reviewer.recall(input);\n if (recalled !== undefined) {\n return recalled\n ? { status: 'approved', decision: 'approve-once' }\n : { status: 'denied', by: 'remembered' };\n }\n // The reviewer bounds its own judgment; a human escalation is bounded only\n // by an explicit request timeout, otherwise by the approval surface itself.\n const decision = await reviewer.review(input, judgment);\n if (decision === 'approve') {\n reviewer.remember(input, 'approve-once', 'reviewer');\n return { status: 'approved', decision: 'approve-once' };\n }\n if (decision === 'ask') {\n if (!isApprovalPortAvailable(ask)) {\n return { status: 'unavailable', reason: 'the reviewer escalated to a human, but nobody can be asked here' };\n }\n const outcome = await settleWithinTimeout(requestApprovalOutcome(ask, input), input);\n // Only an explicit human answer is remembered; a timeout is not a \"no\".\n if (outcome.status === 'approved' || (outcome.status === 'denied' && outcome.by === 'user')) {\n reviewer.remember(input, approvalOutcomeToDecision(outcome), 'human');\n }\n return outcome;\n }\n reviewer.remember(input, 'reject', 'reviewer');\n return { status: 'denied', by: 'reviewer' };\n } catch {\n return { status: 'unavailable', reason: 'approval review failed' };\n }\n },\n async requestApproval(input: ApprovalRequestInput) {\n return (await port.requestApprovalOutcome(input)).status === 'approved';\n },\n };\n return Object.freeze(port);\n}\n\nexport function createBypassApprovalPort(ask?: ApprovalPort): ApprovalOutcomePort {\n const port: ApprovalOutcomePort = {\n available: true,\n async requestApprovalOutcome(input) {\n if (input.signal.aborted) return { status: 'unavailable', reason: 'approval request cancelled' };\n if (input.humanOnly) return isApprovalPortAvailable(ask)\n ? requestApprovalOutcome(ask, input)\n : { status: 'unavailable', reason: 'this tool requires fresh human approval' };\n return { status: 'approved', decision: 'approve-once' };\n },\n requestApproval: async input => (await port.requestApprovalOutcome(input)).status === 'approved',\n };\n return Object.freeze(port);\n}\n\n\nasync function settleBeforeAbort<T>(\n result: Promise<T>,\n signal: AbortSignal,\n cancelled: T,\n): Promise<T> {\n return await new Promise<T>((resolve) => {\n let settled = false;\n const finish = (value: T) => {\n if (settled) return;\n settled = true;\n signal.removeEventListener('abort', deny);\n resolve(value);\n };\n const deny = () => finish(cancelled);\n signal.addEventListener('abort', deny, { once: true });\n // Always attach rejection handling, including when cancellation won the race.\n void result.then(value => finish(value), () => finish(cancelled));\n if (signal.aborted) deny();\n });\n}\n\n/** Bound a human escalation by the caller's explicit timeout; cancellation stays fail-closed. */\nasync function settleWithinTimeout(\n result: Promise<ApprovalOutcome>,\n input: ApprovalRequestInput,\n): Promise<ApprovalOutcome> {\n const cancelled: ApprovalOutcome = { status: 'unavailable', reason: 'the turn was cancelled' };\n const timedOut: ApprovalOutcome = { status: 'timed_out' };\n return await new Promise<ApprovalOutcome>((resolve) => {\n let settled = false;\n const timer = input.timeoutMs === undefined\n ? undefined\n : setTimeout(() => finish(timedOut), input.timeoutMs);\n const onAbort = () => finish(cancelled);\n function finish(value: ApprovalOutcome) {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n input.signal.removeEventListener('abort', onAbort);\n resolve(value);\n }\n input.signal.addEventListener('abort', onAbort, { once: true });\n void result.then(finish, () => finish({ status: 'unavailable', reason: 'the approval prompt failed' }));\n if (input.signal.aborted) onAbort();\n });\n}\n"]}
@@ -36,7 +36,13 @@ function renderTurnReferences(turn) {
36
36
  + 'Use inspect_conversation_reference when the referenced content is needed.';
37
37
  }
38
38
  export function buildTurnSessionCreateInput(turn) {
39
- return { session_key: turn.session.key };
39
+ const origin = turn.origin, im = origin.kind === 'im' ? origin : undefined;
40
+ return { session_key: turn.session.key, source: {
41
+ kind: origin.kind, adapter: im?.conversation?.endpoint.adapter ?? im?.platform,
42
+ endpoint: im?.conversation?.endpoint.id ?? im?.endpoint, scope: im?.conversation?.kind ?? im?.scope, id: im?.conversation?.id ?? im?.sceneId ?? (origin.kind === 'http' ? origin.sessionId : origin.kind === 'schedule' ? origin.jobId : origin.kind === 'a2a' ? origin.taskId : origin.kind === 'internal' ? origin.source : undefined),
43
+ parentKind: im?.conversation?.parent?.kind, parentId: im?.conversation?.parent?.id,
44
+ threadId: im?.conversation?.threadId ?? im?.threadId,
45
+ } };
40
46
  }
41
47
  export async function beginIngressTurnSession(deps, turn) {
42
48
  const record = await deps.agentSessionStore.getOrCreateActive(buildTurnSessionCreateInput(turn));
@@ -1 +1 @@
1
- {"version":3,"file":"turn-ingress-session.js","sourceRoot":"","sources":["../../src/session/turn-ingress-session.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createUserMessage,\n renderUserMessageForLlm,\n type AgentMessageExtra,\n type AgentSessionRepository,\n type CreateAgentSessionInput,\n type UserMessage,\n} from '@zhin.js/ai';\nimport { stripUserSpoofedSenderPrefix } from '@zhin.js/core';\nimport type { TurnIngress } from '../turn/turn-ingress.js';\n\nexport interface ResolvedIngressUserMessage {\n readonly content: string;\n readonly extra?: AgentMessageExtra;\n readonly llmMessage: UserMessage;\n}\n\nexport function resolveIngressUserMessage(\n turn: TurnIngress,\n): ResolvedIngressUserMessage {\n const content = stripUserSpoofedSenderPrefix(turn.input.text);\n const referenceBlock = renderTurnReferences(turn);\n const extra: AgentMessageExtra | undefined = referenceBlock\n ? { quote: { block: referenceBlock } }\n : undefined;\n const llmMessage = renderUserMessageForLlm(\n createUserMessage(content, undefined, Date.now(), {\n subjectId: turn.principal.subjectId,\n ...(turn.principal.displayName ? { displayName: turn.principal.displayName } : {}),\n roles: [...turn.principal.roles],\n ...(turn.principal.origin ? { origin: turn.principal.origin } : {}),\n ...(turn.origin.kind === 'im' ? { scope: turn.origin.scope } : {}),\n }, {\n turnId: turn.identity.turnId,\n intent: turn.intent.kind,\n ...(turn.intent.targetTurnId ? { targetTurnId: turn.intent.targetTurnId } : {}),\n }),\n extra,\n );\n return Object.freeze({\n content,\n ...(extra ? { extra: Object.freeze(extra) } : {}),\n llmMessage,\n });\n}\n\nfunction renderTurnReferences(turn: TurnIngress): string | undefined {\n const references = turn.input.references ?? [];\n if (references.length === 0) return undefined;\n const lines = references.map((reference) => {\n const preview = reference.preview?.trim();\n return `- ${reference.kind} id=${reference.sourceId} reference=${reference.key}`\n + (preview ? `\\n preview: ${preview}` : '');\n });\n return `[Untrusted conversation references]\\n${lines.join('\\n')}\\n`\n + 'Use inspect_conversation_reference when the referenced content is needed.';\n}\n\nexport function buildTurnSessionCreateInput(\n turn: TurnIngress,\n): CreateAgentSessionInput {\n return { session_key: turn.session.key };\n}\n\nexport async function beginIngressTurnSession(\n deps: { agentSessionStore: AgentSessionRepository },\n turn: TurnIngress,\n): Promise<{ sessionKey: string; sessionId: string }> {\n const record = await deps.agentSessionStore.getOrCreateActive(\n buildTurnSessionCreateInput(turn),\n );\n return { sessionKey: turn.session.key, sessionId: record.session_id };\n}\n"]}
1
+ {"version":3,"file":"turn-ingress-session.js","sourceRoot":"","sources":["../../src/session/turn-ingress-session.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createUserMessage,\n renderUserMessageForLlm,\n type AgentMessageExtra,\n type AgentSessionRepository,\n type CreateAgentSessionInput,\n type UserMessage,\n} from '@zhin.js/ai';\nimport { stripUserSpoofedSenderPrefix } from '@zhin.js/core';\nimport type { TurnIngress } from '../turn/turn-ingress.js';\n\nexport interface ResolvedIngressUserMessage {\n readonly content: string;\n readonly extra?: AgentMessageExtra;\n readonly llmMessage: UserMessage;\n}\n\nexport function resolveIngressUserMessage(\n turn: TurnIngress,\n): ResolvedIngressUserMessage {\n const content = stripUserSpoofedSenderPrefix(turn.input.text);\n const referenceBlock = renderTurnReferences(turn);\n const extra: AgentMessageExtra | undefined = referenceBlock\n ? { quote: { block: referenceBlock } }\n : undefined;\n const llmMessage = renderUserMessageForLlm(\n createUserMessage(content, undefined, Date.now(), {\n subjectId: turn.principal.subjectId,\n ...(turn.principal.displayName ? { displayName: turn.principal.displayName } : {}),\n roles: [...turn.principal.roles],\n ...(turn.principal.origin ? { origin: turn.principal.origin } : {}),\n ...(turn.origin.kind === 'im' ? { scope: turn.origin.scope } : {}),\n }, {\n turnId: turn.identity.turnId,\n intent: turn.intent.kind,\n ...(turn.intent.targetTurnId ? { targetTurnId: turn.intent.targetTurnId } : {}),\n }),\n extra,\n );\n return Object.freeze({\n content,\n ...(extra ? { extra: Object.freeze(extra) } : {}),\n llmMessage,\n });\n}\n\nfunction renderTurnReferences(turn: TurnIngress): string | undefined {\n const references = turn.input.references ?? [];\n if (references.length === 0) return undefined;\n const lines = references.map((reference) => {\n const preview = reference.preview?.trim();\n return `- ${reference.kind} id=${reference.sourceId} reference=${reference.key}`\n + (preview ? `\\n preview: ${preview}` : '');\n });\n return `[Untrusted conversation references]\\n${lines.join('\\n')}\\n`\n + 'Use inspect_conversation_reference when the referenced content is needed.';\n}\n\nexport function buildTurnSessionCreateInput(\n turn: TurnIngress,\n): CreateAgentSessionInput {\n const origin = turn.origin, im = origin.kind === 'im' ? origin : undefined;\n return { session_key: turn.session.key, source: {\n kind: origin.kind, adapter: im?.conversation?.endpoint.adapter ?? im?.platform,\n endpoint: im?.conversation?.endpoint.id ?? im?.endpoint, scope: im?.conversation?.kind ?? im?.scope, id: im?.conversation?.id ?? im?.sceneId ?? (origin.kind === 'http' ? origin.sessionId : origin.kind === 'schedule' ? origin.jobId : origin.kind === 'a2a' ? origin.taskId : origin.kind === 'internal' ? origin.source : undefined),\n parentKind: im?.conversation?.parent?.kind, parentId: im?.conversation?.parent?.id,\n threadId: im?.conversation?.threadId ?? im?.threadId,\n } };\n}\n\nexport async function beginIngressTurnSession(\n deps: { agentSessionStore: AgentSessionRepository },\n turn: TurnIngress,\n): Promise<{ sessionKey: string; sessionId: string }> {\n const record = await deps.agentSessionStore.getOrCreateActive(\n buildTurnSessionCreateInput(turn),\n );\n return { sessionKey: turn.session.key, sessionId: record.session_id };\n}\n"]}
@@ -3,7 +3,7 @@ import { isApprovalPortAvailable } from '../session/approval-port.js';
3
3
  export async function resolveToolApprovalRequired(policy, input, onceStore) {
4
4
  if (!policy || policy === 'never')
5
5
  return false;
6
- if (policy === 'always' || policy === 'on-risk')
6
+ if (policy === 'always' || policy === 'on-risk' || policy === 'human')
7
7
  return true;
8
8
  if (policy === 'once') {
9
9
  if (!onceStore)
@@ -43,11 +43,13 @@ export async function runToolApprovalGate(options) {
43
43
  args: options.args,
44
44
  },
45
45
  }, publishCtx);
46
- const question = `工具「${options.toolName}」需要确认后执行。是否继续?`;
46
+ const question = `工具「${options.toolName}」需要确认后执行。是否继续?`
47
+ + (options.policy === 'human' ? `\n本次参数:${stableApprovalValue(options.args)}` : '');
47
48
  let approved = false;
48
49
  let failure;
49
50
  try {
50
51
  approved = await options.port.requestApproval({
52
+ ...(options.policy === 'human' ? { humanOnly: true } : {}),
51
53
  requestId,
52
54
  sessionKey: options.sessionId,
53
55
  conversationScope: options.commMessage.conversation.kind,