@zhin.js/agent 1.1.39 → 1.1.41

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 (120) 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/decision/config.d.ts +1 -1
  37. package/lib/decision/config.js +1 -1
  38. package/lib/decision/config.js.map +1 -1
  39. package/lib/decision/types.d.ts +1 -1
  40. package/lib/decision/types.js.map +1 -1
  41. package/lib/event/turn-event.d.ts +7 -1
  42. package/lib/event/turn-event.js.map +1 -1
  43. package/lib/index.d.ts +4 -1
  44. package/lib/index.js +2 -0
  45. package/lib/index.js.map +1 -1
  46. package/lib/media/perception.d.ts +3 -4
  47. package/lib/media/perception.js +59 -37
  48. package/lib/media/perception.js.map +1 -1
  49. package/lib/memory/compaction-runtime.d.ts +3 -2
  50. package/lib/memory/compaction-runtime.js +22 -14
  51. package/lib/memory/compaction-runtime.js.map +1 -1
  52. package/lib/outbound/agent-output.js +2 -0
  53. package/lib/outbound/agent-output.js.map +1 -1
  54. package/lib/outbound/chat-failure.d.ts +27 -0
  55. package/lib/outbound/chat-failure.js +53 -0
  56. package/lib/outbound/chat-failure.js.map +1 -0
  57. package/lib/outbound/structured-reply.d.ts +7 -0
  58. package/lib/outbound/structured-reply.js +52 -16
  59. package/lib/outbound/structured-reply.js.map +1 -1
  60. package/lib/plugin-runtime/agent-runtime.js +49 -8
  61. package/lib/plugin-runtime/agent-runtime.js.map +1 -1
  62. package/lib/plugin-runtime/ai-completion.d.ts +4 -3
  63. package/lib/plugin-runtime/ai-completion.js +64 -10
  64. package/lib/plugin-runtime/ai-completion.js.map +1 -1
  65. package/lib/plugin-runtime/capability-ingress.js +3 -1
  66. package/lib/plugin-runtime/capability-ingress.js.map +1 -1
  67. package/lib/plugin-runtime/decision-runtime.d.ts +1 -0
  68. package/lib/plugin-runtime/deferred-capability-plan.d.ts +4 -1
  69. package/lib/plugin-runtime/deferred-capability-plan.js +25 -9
  70. package/lib/plugin-runtime/deferred-capability-plan.js.map +1 -1
  71. package/lib/plugin-runtime/full-agent-turn-engine.js +1 -0
  72. package/lib/plugin-runtime/full-agent-turn-engine.js.map +1 -1
  73. package/lib/plugin-runtime/index.d.ts +2 -0
  74. package/lib/plugin-runtime/index.js +2 -0
  75. package/lib/plugin-runtime/index.js.map +1 -1
  76. package/lib/prompt/assembly.js +0 -9
  77. package/lib/prompt/assembly.js.map +1 -1
  78. package/lib/prompt/git-context.js +1 -1
  79. package/lib/prompt/git-context.js.map +1 -1
  80. package/lib/prompt/rules/chat-rules.js +8 -9
  81. package/lib/prompt/rules/chat-rules.js.map +1 -1
  82. package/lib/prompt/rules/critical-rules.js +10 -10
  83. package/lib/prompt/rules/critical-rules.js.map +1 -1
  84. package/lib/prompt/system-prompt.js +4 -5
  85. package/lib/prompt/system-prompt.js.map +1 -1
  86. package/lib/security/file-role-policy.js +6 -6
  87. package/lib/security/file-role-policy.js.map +1 -1
  88. package/lib/security/policy-facade.js +1 -1
  89. package/lib/security/policy-facade.js.map +1 -1
  90. package/lib/security/usage-quota.d.ts +4 -0
  91. package/lib/security/usage-quota.js +23 -2
  92. package/lib/security/usage-quota.js.map +1 -1
  93. package/lib/session/approval-port.d.ts +2 -0
  94. package/lib/session/approval-port.js.map +1 -1
  95. package/lib/session/approval-review-agent.d.ts +1 -1
  96. package/lib/session/approval-review-agent.js +19 -4
  97. package/lib/session/approval-review-agent.js.map +1 -1
  98. package/lib/session/turn-ingress-session.js +7 -1
  99. package/lib/session/turn-ingress-session.js.map +1 -1
  100. package/lib/tool/tool-approval-gate.js +4 -2
  101. package/lib/tool/tool-approval-gate.js.map +1 -1
  102. package/lib/tool/turn-tool-runtime.js +24 -5
  103. package/lib/tool/turn-tool-runtime.js.map +1 -1
  104. package/lib/tool-catalog/deferred-turn-controller.js +1 -4
  105. package/lib/tool-catalog/deferred-turn-controller.js.map +1 -1
  106. package/lib/trace/context-budget.d.ts +4 -3
  107. package/lib/trace/context-budget.js +28 -8
  108. package/lib/trace/context-budget.js.map +1 -1
  109. package/lib/trace/turn-trace-format.js +4 -1
  110. package/lib/trace/turn-trace-format.js.map +1 -1
  111. package/lib/trace/turn-trace.d.ts +6 -0
  112. package/lib/trace/turn-trace.js +8 -2
  113. package/lib/trace/turn-trace.js.map +1 -1
  114. package/lib/trace/turn-usage-log.d.ts +14 -0
  115. package/lib/trace/turn-usage-log.js +83 -0
  116. package/lib/trace/turn-usage-log.js.map +1 -0
  117. package/lib/usage/usage-store.d.ts +199 -0
  118. package/lib/usage/usage-store.js +121 -0
  119. package/lib/usage/usage-store.js.map +1 -0
  120. package/package.json +12 -12
@@ -1 +1 @@
1
- {"version":3,"file":"usage-quota.js","sourceRoot":"","sources":["../../src/security/usage-quota.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAeH,MAAM,CAAC,MAAM,mBAAmB,GAAuB,MAAM,CAAC,MAAM,CAAC;IACnE,QAAQ,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;IAC7B,aAAa,EAAE,CAAC;IAChB,cAAc,EAAE,CAAC;IACjB,yBAAyB,EAAE,CAAC;IAC5B,WAAW,EAAE,IAAI;CAClB,CAAC,CAAC;AAEH,MAAM,UAAU,iBAAiB,CAAC,GAAiC;IACjE,MAAM,KAAK,GAAG,CAAC,KAAc,EAAE,QAAgB,EAAE,EAAE,CACjD,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACnG,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,EAAE,QAAQ,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QAC9E,aAAa,EAAE,KAAK,CAAC,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC;QAC3C,cAAc,EAAE,KAAK,CAAC,GAAG,EAAE,cAAc,EAAE,CAAC,CAAC;QAC7C,yBAAyB,EAAE,KAAK,CAAC,GAAG,EAAE,yBAAyB,EAAE,CAAC,CAAC;QACnE,WAAW,EAAE,GAAG,EAAE,WAAW,KAAK,KAAK;KACxC,CAAC,CAAC;AACL,CAAC;AAgBD,MAAM,OAAO,uBAAuB;IACzB,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;IACnD,GAAG,CAAC,GAAW,IAAI,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACnD,GAAG,CAAC,GAAW,EAAE,MAAmB,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,CAAC,GAAW,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAClD,IAAI,KAAK,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;CACxC;AAUD,MAAM,cAAc,GAAG,MAAM,CAAC;AAE9B,MAAM,OAAO,UAAU;IACZ,QAAQ,CAAqB;IAC7B,MAAM,CAAkB;IACxB,IAAI,CAAe;IAE5B,YAAY,QAAsC,EAAE,QAAyB,IAAI,uBAAuB,EAAE,EAAE,MAAoB,IAAI,CAAC,GAAG;QACtI,IAAI,CAAC,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;IAClB,CAAC;IAED,IAAI,OAAO;QACT,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;QACxB,OAAO,CAAC,CAAC,aAAa,GAAG,CAAC,IAAI,CAAC,CAAC,cAAc,GAAG,CAAC,IAAI,CAAC,CAAC,yBAAyB,GAAG,CAAC,CAAC;IACxF,CAAC;IAED,6FAA6F;IAC7F,KAAK,CAAC,OAAqB;QACzB,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC5F,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACxB,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,yBAAyB,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC7F,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,eAAe,OAAO,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;YAC7E,IAAI,MAAM,CAAC,IAAI,IAAI,aAAa,EAAE,CAAC;gBACjC,OAAO,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EACjD,qBAAqB,aAAa,cAAc,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;YACrG,CAAC;QACH,CAAC;QACD,IAAI,OAAO,CAAC,OAAO,IAAI,cAAc,GAAG,CAAC,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,gBAAgB,OAAO,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;YAC/E,IAAI,MAAM,CAAC,IAAI,IAAI,cAAc,EAAE,CAAC;gBAClC,OAAO,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAClD,wBAAwB,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,iBAAiB,CAAC,CAAC;YAChF,CAAC;QACH,CAAC;QACD,IAAI,OAAO,CAAC,OAAO,IAAI,yBAAyB,GAAG,CAAC,EAAE,CAAC;YACrD,MAAM,GAAG,GAAG,cAAc,OAAO,CAAC,OAAO,EAAE,CAAC;YAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,CAAC,CAAC;YACvD,IAAI,MAAM,CAAC,IAAI,IAAI,yBAAyB,EAAE,CAAC;gBAC7C,OAAO,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,EACtD,cAAc,UAAU,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;YACjE,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC;QACnF,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAED,iFAAiF;IACjF,MAAM,CAAC,OAAqB,EAAE,WAAmB;QAC/C,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,WAAW,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC;YAAE,OAAO;QAC9F,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACxB,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;QAClE,IAAI,aAAa,GAAG,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,eAAe,OAAO,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;QAC9F,IAAI,OAAO,CAAC,OAAO,IAAI,cAAc,GAAG,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,gBAAgB,OAAO,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;IACtH,CAAC;IAED,8BAA8B;IAC9B,KAAK;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACxB,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACpC,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACnF,IAAI,CAAC,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,WAAW,IAAI,QAAQ;gBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,GAAW,EAAE,QAAgB,EAAE,GAAW;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,WAAW,IAAI,QAAQ;YAAE,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAC1F,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,CAAC,GAAW,EAAE,QAAgB,EAAE,GAAW,EAAE,MAAc;QAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,GAAG,MAAM,EAAE,CAAC,CAAC;IACxF,CAAC;CACF;AAED,SAAS,IAAI,CACX,KAAuB,EACvB,MAAyB,EACzB,MAAmB,EACnB,QAAgB,EAChB,GAAW,EACX,OAAe;IAEf,MAAM,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,WAAW,GAAG,QAAQ,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAC/F,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC,CAAC;AACtF,CAAC;AAED,SAAS,UAAU,CAAC,MAAmB,EAAE,QAAgB,EAAE,GAAW;IACpE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,WAAW,GAAG,QAAQ,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IACrF,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,OAAO,IAAI,CAAC;IACxC,IAAI,OAAO,GAAG,IAAI;QAAE,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC;IAC3D,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC;AAC3C,CAAC","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,
@@ -1 +1 @@
1
- {"version":3,"file":"tool-approval-gate.js","sourceRoot":"","sources":["../../src/tool/tool-approval-gate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Per-tool approval gate — stacks with ExecPolicy (ADR 0039 P1).\n */\nimport type { Message } from '@zhin.js/core';\nimport { AgentRunJournal, AgentStreamEventType, type AgentRunEventInput } from '@zhin.js/ai/agent-stream';\nimport type { ToolApprovalPolicy } from '@zhin.js/ai/tool-policy';\nimport type { AgentStreamBus, AgentStreamPublishContext } from '../event/agent-stream-bus.js';\nimport { isApprovalPortAvailable, type ApprovalPort } from '../session/approval-port.js';\nimport type { ToolApprovalOnceStore } from './tool-approval-once-store.js';\n\nexport type ToolApprovalGatePolicy = ToolApprovalPolicy;\n\nexport async function resolveToolApprovalRequired(\n policy: ToolApprovalGatePolicy | undefined,\n input: { toolName: string; args: Record<string, unknown>; sessionId: string },\n onceStore?: ToolApprovalOnceStore,\n): Promise<boolean> {\n if (!policy || policy === 'never') return false;\n if (policy === 'always' || policy === 'on-risk') return true;\n if (policy === 'once') {\n if (!onceStore) return true;\n return !onceStore.has(input.sessionId, input.toolName);\n }\n return Boolean(await policy({ toolName: input.toolName, args: input.args }));\n}\n\nexport interface ToolApprovalGateOptions {\n toolName: string;\n args: Record<string, unknown>;\n sessionId: string;\n commMessage: Message;\n policy?: ToolApprovalGatePolicy;\n bus?: AgentStreamBus;\n port?: ApprovalPort;\n onceStore?: ToolApprovalOnceStore;\n publishCtx?: AgentStreamPublishContext;\n /** Stream-turn journal; request/result become ordered run events when present. */\n journal?: AgentRunJournal;\n signal: AbortSignal;\n}\n\n/**\n * Returns denial message when approval is required but not granted; otherwise null.\n */\nexport async function runToolApprovalGate(\n options: ToolApprovalGateOptions,\n): Promise<string | null> {\n const required = await resolveToolApprovalRequired(options.policy, {\n toolName: options.toolName,\n args: options.args,\n sessionId: options.sessionId,\n }, options.onceStore);\n if (!required) return null;\n\n if (!isApprovalPortAvailable(options.port)) {\n return 'Error: approval required but ApprovalPort unavailable';\n }\n\n const requestId = `approval_${options.toolName}_${Date.now()}`;\n const publishCtx: AgentStreamPublishContext = {\n sessionId: options.sessionId,\n ...options.publishCtx,\n };\n\n await publishApprovalEvent(options, {\n type: AgentStreamEventType.INPUT_REQUESTED,\n data: {\n sessionId: options.sessionId,\n requestId,\n sessionKey: options.sessionId,\n requesterId: options.commMessage.sender?.id,\n toolName: options.toolName,\n kind: 'approval',\n args: options.args,\n },\n }, publishCtx);\n\n const question = `工具「${options.toolName}」需要确认后执行。是否继续?`;\n let approved = false;\n let failure: string | undefined;\n try {\n approved = await options.port.requestApproval({\n requestId,\n sessionKey: options.sessionId,\n conversationScope: options.commMessage.conversation.kind,\n requesterId: options.commMessage.sender?.id,\n toolName: options.toolName,\n scopeKey: `${options.toolName}:${stableApprovalValue(options.args)}`,\n question,\n signal: options.signal,\n });\n } catch (err) {\n failure = err instanceof Error ? err.message : String(err);\n }\n\n await publishApprovalEvent(options, {\n type: AgentStreamEventType.INPUT_COMPLETED,\n data: {\n sessionId: options.sessionId,\n requestId,\n toolName: options.toolName,\n kind: 'approval',\n approved,\n ...(failure ? { error: failure } : {}),\n },\n }, publishCtx);\n\n if (failure) return `Error: approval wait failed — ${failure}`;\n\n if (!approved) {\n return `Error: tool \"${options.toolName}\" execution denied by user`;\n }\n\n if (options.policy === 'once' && options.onceStore) {\n options.onceStore.add(options.sessionId, options.toolName);\n }\n return null;\n}\n\nfunction stableApprovalValue(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableApprovalValue).join(',')}]`;\n if (value && typeof value === 'object') {\n return `{${Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, entry]) => `${JSON.stringify(key)}:${stableApprovalValue(entry)}`)\n .join(',')}}`;\n }\n if (typeof value === 'undefined') return 'undefined';\n try { return JSON.stringify(value); } catch { return String(value); }\n}\n\nasync function publishApprovalEvent(\n options: ToolApprovalGateOptions,\n event: AgentRunEventInput,\n ctx: AgentStreamPublishContext,\n): Promise<void> {\n const journalled = options.journal?.append(event);\n if (options.journal && !journalled) return;\n if (options.bus) await options.bus.publish(journalled ?? event, ctx);\n}\n"]}
1
+ {"version":3,"file":"tool-approval-gate.js","sourceRoot":"","sources":["../../src/tool/tool-approval-gate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Per-tool approval gate — stacks with ExecPolicy (ADR 0039 P1).\n */\nimport type { Message } from '@zhin.js/core';\nimport { AgentRunJournal, AgentStreamEventType, type AgentRunEventInput } from '@zhin.js/ai/agent-stream';\nimport type { ToolApprovalPolicy } from '@zhin.js/ai/tool-policy';\nimport type { AgentStreamBus, AgentStreamPublishContext } from '../event/agent-stream-bus.js';\nimport { isApprovalPortAvailable, type ApprovalPort } from '../session/approval-port.js';\nimport type { ToolApprovalOnceStore } from './tool-approval-once-store.js';\n\nexport type ToolApprovalGatePolicy = ToolApprovalPolicy;\n\nexport async function resolveToolApprovalRequired(\n policy: ToolApprovalGatePolicy | undefined,\n input: { toolName: string; args: Record<string, unknown>; sessionId: string },\n onceStore?: ToolApprovalOnceStore,\n): Promise<boolean> {\n if (!policy || policy === 'never') return false;\n if (policy === 'always' || policy === 'on-risk' || policy === 'human') return true;\n if (policy === 'once') {\n if (!onceStore) return true;\n return !onceStore.has(input.sessionId, input.toolName);\n }\n return Boolean(await policy({ toolName: input.toolName, args: input.args }));\n}\n\nexport interface ToolApprovalGateOptions {\n toolName: string;\n args: Record<string, unknown>;\n sessionId: string;\n commMessage: Message;\n policy?: ToolApprovalGatePolicy;\n bus?: AgentStreamBus;\n port?: ApprovalPort;\n onceStore?: ToolApprovalOnceStore;\n publishCtx?: AgentStreamPublishContext;\n /** Stream-turn journal; request/result become ordered run events when present. */\n journal?: AgentRunJournal;\n signal: AbortSignal;\n}\n\n/**\n * Returns denial message when approval is required but not granted; otherwise null.\n */\nexport async function runToolApprovalGate(\n options: ToolApprovalGateOptions,\n): Promise<string | null> {\n const required = await resolveToolApprovalRequired(options.policy, {\n toolName: options.toolName,\n args: options.args,\n sessionId: options.sessionId,\n }, options.onceStore);\n if (!required) return null;\n\n if (!isApprovalPortAvailable(options.port)) {\n return 'Error: approval required but ApprovalPort unavailable';\n }\n\n const requestId = `approval_${options.toolName}_${Date.now()}`;\n const publishCtx: AgentStreamPublishContext = {\n sessionId: options.sessionId,\n ...options.publishCtx,\n };\n\n await publishApprovalEvent(options, {\n type: AgentStreamEventType.INPUT_REQUESTED,\n data: {\n sessionId: options.sessionId,\n requestId,\n sessionKey: options.sessionId,\n requesterId: options.commMessage.sender?.id,\n toolName: options.toolName,\n kind: 'approval',\n args: options.args,\n },\n }, publishCtx);\n\n const question = `工具「${options.toolName}」需要确认后执行。是否继续?`\n + (options.policy === 'human' ? `\\n本次参数:${stableApprovalValue(options.args)}` : '');\n let approved = false;\n let failure: string | undefined;\n try {\n approved = await options.port.requestApproval({\n ...(options.policy === 'human' ? { humanOnly: true } : {}),\n requestId,\n sessionKey: options.sessionId,\n conversationScope: options.commMessage.conversation.kind,\n requesterId: options.commMessage.sender?.id,\n toolName: options.toolName,\n scopeKey: `${options.toolName}:${stableApprovalValue(options.args)}`,\n question,\n signal: options.signal,\n });\n } catch (err) {\n failure = err instanceof Error ? err.message : String(err);\n }\n\n await publishApprovalEvent(options, {\n type: AgentStreamEventType.INPUT_COMPLETED,\n data: {\n sessionId: options.sessionId,\n requestId,\n toolName: options.toolName,\n kind: 'approval',\n approved,\n ...(failure ? { error: failure } : {}),\n },\n }, publishCtx);\n\n if (failure) return `Error: approval wait failed — ${failure}`;\n\n if (!approved) {\n return `Error: tool \"${options.toolName}\" execution denied by user`;\n }\n\n if (options.policy === 'once' && options.onceStore) {\n options.onceStore.add(options.sessionId, options.toolName);\n }\n return null;\n}\n\nfunction stableApprovalValue(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableApprovalValue).join(',')}]`;\n if (value && typeof value === 'object') {\n return `{${Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, entry]) => `${JSON.stringify(key)}:${stableApprovalValue(entry)}`)\n .join(',')}}`;\n }\n if (typeof value === 'undefined') return 'undefined';\n try { return JSON.stringify(value); } catch { return String(value); }\n}\n\nasync function publishApprovalEvent(\n options: ToolApprovalGateOptions,\n event: AgentRunEventInput,\n ctx: AgentStreamPublishContext,\n): Promise<void> {\n const journalled = options.journal?.append(event);\n if (options.journal && !journalled) return;\n if (options.bus) await options.bus.publish(journalled ?? event, ctx);\n}\n"]}
@@ -39,6 +39,22 @@ export class TurnToolRuntime {
39
39
  }
40
40
  let effectiveInput = decision.input;
41
41
  const highRisk = classifyHighRiskAction(tool);
42
+ const humanOnly = tool.requiresApproval === 'human';
43
+ if (humanOnly) {
44
+ try {
45
+ // Caller-owned nested values must not change the operation while a person reviews it.
46
+ effectiveInput = structuredClone(effectiveInput);
47
+ // Reject cycles and other non-serializable input before constructing the approval card.
48
+ JSON.stringify(effectiveInput);
49
+ if (highRisk === 'outbound' && this.#leakInspector) {
50
+ // Show exactly what will execute; do not redact approved arguments afterward.
51
+ effectiveInput = this.#leakInspector.inspectArguments(effectiveInput).input;
52
+ }
53
+ }
54
+ catch {
55
+ return this.#deny(name, toolUseId, 'approval', 'human approval requires an isolated JSON argument snapshot', causedBy);
56
+ }
57
+ }
42
58
  const provenanceGate = highRisk !== undefined
43
59
  && this.#guard.confirmHighRisk === 'external_content'
44
60
  && this.provenance.tainted;
@@ -50,6 +66,7 @@ export class TurnToolRuntime {
50
66
  }
51
67
  const chain = this.provenance.describeChain(resolveTurnTrustLevel(this.turn), name);
52
68
  const approval = await requestApprovalOutcome(this.turn.ports.approval, {
69
+ ...(humanOnly ? { humanOnly: true } : {}),
53
70
  requestId: `${this.turn.identity.turnId}:${toolUseId}`,
54
71
  sessionKey: this.turn.session.key,
55
72
  ...(this.turn.origin.kind === 'im'
@@ -59,7 +76,7 @@ export class TurnToolRuntime {
59
76
  toolName: name,
60
77
  // Never matches a remembered approval: each tainted call is confirmed on its own.
61
78
  scopeKey: `${PROVENANCE_POLICY}:${this.turn.identity.turnId}:${toolUseId}`,
62
- question: formatApprovalQuestion(name, formatProvenanceReason(highRisk, chain), effectiveInput),
79
+ question: formatApprovalQuestion(name, formatProvenanceReason(highRisk, chain), effectiveInput, humanOnly),
63
80
  signal: this.turn.signal,
64
81
  });
65
82
  if (this.turn.signal.aborted)
@@ -73,6 +90,7 @@ export class TurnToolRuntime {
73
90
  return this.#deny(name, toolUseId, decision.policy, 'approval required but ApprovalPort unavailable', causedBy);
74
91
  }
75
92
  const approval = await requestApprovalOutcome(this.turn.ports.approval, {
93
+ ...(humanOnly ? { humanOnly: true } : {}),
76
94
  requestId: `${this.turn.identity.turnId}:${toolUseId}`,
77
95
  sessionKey: this.turn.session.key,
78
96
  ...(this.turn.origin.kind === 'im'
@@ -82,11 +100,11 @@ export class TurnToolRuntime {
82
100
  toolName: name,
83
101
  scopeKey: tool.requiresApproval === 'once'
84
102
  ? name
85
- : approvalScopeKey(name, decision.input),
103
+ : approvalScopeKey(name, effectiveInput),
86
104
  ...(tool.requiresApproval === 'once' || decision.policy === 'exec-policy'
87
105
  ? { remember: 'session' }
88
106
  : {}),
89
- question: formatApprovalQuestion(name, decision.reason, decision.input),
107
+ question: formatApprovalQuestion(name, decision.reason, effectiveInput, humanOnly),
90
108
  signal: this.turn.signal,
91
109
  });
92
110
  if (this.turn.signal.aborted)
@@ -95,7 +113,7 @@ export class TurnToolRuntime {
95
113
  return this.#deny(name, toolUseId, decision.policy, formatApprovalDenial(approval), causedBy);
96
114
  }
97
115
  }
98
- if (highRisk === 'outbound' && this.#leakInspector) {
116
+ if (!humanOnly && highRisk === 'outbound' && this.#leakInspector) {
99
117
  // #727 M3.4: messages to other conversations go through the same leak check as replies.
100
118
  effectiveInput = this.#leakInspector.inspectArguments(effectiveInput).input;
101
119
  }
@@ -160,7 +178,7 @@ export class TurnToolRuntime {
160
178
  }
161
179
  }
162
180
  }
163
- function formatApprovalQuestion(toolName, reason, input) {
181
+ function formatApprovalQuestion(toolName, reason, input, humanOnly) {
164
182
  const command = typeof input.command === 'string' ? input.command.trim() : '';
165
183
  const cwd = typeof input.cwd === 'string' ? input.cwd.trim() : '';
166
184
  const path = ['file_path', 'path'].map((key) => input[key]).find((value) => typeof value === 'string');
@@ -169,6 +187,7 @@ function formatApprovalQuestion(toolName, reason, input) {
169
187
  command ? `命令:${command}` : undefined,
170
188
  cwd ? `工作目录:${cwd}` : undefined,
171
189
  path ? `目标文件:${path}` : undefined,
190
+ humanOnly ? `本次参数:${stableApprovalValue(input)}` : undefined,
172
191
  `风险:${reason}`,
173
192
  '是否继续?',
174
193
  ].filter((line) => Boolean(line)).join('\n');
@@ -1 +1 @@
1
- {"version":3,"file":"turn-tool-runtime.js","sourceRoot":"","sources":["../../src/tool/turn-tool-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { runTurnToolPolicies } from '../security/policy-facade.js';\nimport type { ToolCapability } from '../plugin-runtime/capability-ingress.js';\nimport { toolInvocationFromTurn } from '../plugin-runtime/capability-tools.js';\nimport type { TurnIngress } from '../turn/turn-ingress.js';\nimport {\n formatApprovalDenial,\n isApprovalPortAvailable,\n requestApprovalOutcome,\n} from '../session/approval-port.js';\nimport type { AgentTool, ToolExecutionCause } from '@zhin.js/ai';\nimport type { ToolExecutionAuthority } from '../core/tool-execution-authority.js';\nimport { TurnJournalCommitError } from '../turn/journal-integrity.js';\nimport { NetworkAccessDeniedError } from '../security/network-policy.js';\nimport {\n PROVENANCE_POLICY,\n PROVENANCE_UNATTENDED_REASON,\n TurnProvenance,\n classifyHighRiskAction,\n formatProvenanceReason,\n isExternalContentSource,\n resolveInjectionGuard,\n type InjectionGuardSettings,\n} from '../security/provenance.js';\nimport type { OutboundLeakInspector } from '../security/outbound-leak-guard.js';\nimport { resolveTurnTrustLevel } from '../security/trust-level.js';\nimport { safeSessionKey } from '../memory-layers.js';\n\ntype ToolJournalEvent = Extract<import('../event/turn-event.js').TurnEvent, {\n type: 'tool_call' | 'tool_result' | 'tool_denied' | 'tool_failed' | 'tool_cancelled';\n}>;\n\nexport type TurnToolOutcome =\n | Readonly<{ status: 'completed'; output: unknown; durationMs: number }>\n | Readonly<{ status: 'denied'; policy: string; reason: string }>\n | Readonly<{ status: 'cancelled'; reason: string }>\n | Readonly<{ status: 'failed'; error: string; retryable: boolean }>;\n\nexport interface TurnToolRuntimeOptions {\n /** `ai.agent.injectionGuard`; unset keys use the conservative defaults. */\n readonly injectionGuard?: Partial<InjectionGuardSettings>;\n /** Shared with the engine so sources outside tool calls (background events) also count. */\n readonly provenance?: TurnProvenance;\n /** Masks leaks in the text arguments of outbound tools and learns other-session memory reads. */\n readonly leakInspector?: OutboundLeakInspector;\n}\n\n/** Sole execution authority for Tool capabilities within one fixed Turn lease. */\nexport class TurnToolRuntime {\n readonly #tools: ReadonlyMap<string, ToolCapability>;\n readonly #guard: InjectionGuardSettings;\n readonly #leakInspector?: OutboundLeakInspector;\n /** Lower-trust sources that reached the model during this turn (#727). */\n readonly provenance: TurnProvenance;\n\n constructor(\n private readonly turn: TurnIngress,\n tools: readonly ToolCapability[],\n options: TurnToolRuntimeOptions = {},\n ) {\n this.#tools = new Map(tools.map((tool) => [tool.name, tool]));\n this.#guard = resolveInjectionGuard(options.injectionGuard);\n this.provenance = options.provenance ?? new TurnProvenance();\n this.#leakInspector = this.#guard.outboundCheck === 'redact' ? options.leakInspector : undefined;\n }\n\n async execute(\n name: string,\n input: Readonly<Record<string, unknown>>,\n toolUseId: string,\n cause?: ToolExecutionCause,\n ): Promise<TurnToolOutcome> {\n const tool = this.#tools.get(name);\n const causedBy = freezeToolExecutionCause(cause);\n if (!tool) return this.#deny(name, toolUseId, 'capability', `Unknown Tool capability: ${name}`, causedBy);\n await this.#append({ type: 'tool_call', toolName: name, args: { ...input }, toolUseId }, causedBy);\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n const decision = await runTurnToolPolicies({ turn: this.turn, tool, input });\n // File/cwd authorization is asynchronous: cancellation can arrive after\n // the ingress check but before an allowed decision reaches execution.\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n if (decision.status === 'denied') {\n return this.#deny(name, toolUseId, decision.policy, decision.reason, causedBy);\n }\n let effectiveInput = decision.input;\n const highRisk = classifyHighRiskAction(tool);\n const provenanceGate = highRisk !== undefined\n && this.#guard.confirmHighRisk === 'external_content'\n && this.provenance.tainted;\n if (provenanceGate) {\n // #727 M3.3: a high-risk action after lower-trust content always asks\n // first, even when the configured policy would allow it outright.\n if (this.turn.policy.unattended || !isApprovalPortAvailable(this.turn.ports.approval)) {\n return this.#deny(name, toolUseId, PROVENANCE_POLICY, PROVENANCE_UNATTENDED_REASON, causedBy);\n }\n const chain = this.provenance.describeChain(resolveTurnTrustLevel(this.turn), name);\n const approval = await requestApprovalOutcome(this.turn.ports.approval, {\n requestId: `${this.turn.identity.turnId}:${toolUseId}`,\n sessionKey: this.turn.session.key,\n ...(this.turn.origin.kind === 'im'\n ? { conversationScope: this.turn.origin.scope }\n : {}),\n requesterId: this.turn.principal.subjectId,\n toolName: name,\n // Never matches a remembered approval: each tainted call is confirmed on its own.\n scopeKey: `${PROVENANCE_POLICY}:${this.turn.identity.turnId}:${toolUseId}`,\n question: formatApprovalQuestion(name, formatProvenanceReason(highRisk, chain), effectiveInput),\n signal: this.turn.signal,\n });\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n if (approval.status !== 'approved') {\n return this.#deny(name, toolUseId, PROVENANCE_POLICY, formatApprovalDenial(approval), causedBy);\n }\n } else if (decision.status === 'approval_required') {\n if (this.turn.policy.unattended || !isApprovalPortAvailable(this.turn.ports.approval)) {\n return this.#deny(name, toolUseId, decision.policy, 'approval required but ApprovalPort unavailable', causedBy);\n }\n const approval = await requestApprovalOutcome(this.turn.ports.approval, {\n requestId: `${this.turn.identity.turnId}:${toolUseId}`,\n sessionKey: this.turn.session.key,\n ...(this.turn.origin.kind === 'im'\n ? { conversationScope: this.turn.origin.scope }\n : {}),\n requesterId: this.turn.principal.subjectId,\n toolName: name,\n scopeKey: tool.requiresApproval === 'once'\n ? name\n : approvalScopeKey(name, decision.input),\n ...(tool.requiresApproval === 'once' || decision.policy === 'exec-policy'\n ? { remember: 'session' as const }\n : {}),\n question: formatApprovalQuestion(name, decision.reason, decision.input),\n signal: this.turn.signal,\n });\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n if (approval.status !== 'approved') {\n return this.#deny(name, toolUseId, decision.policy, formatApprovalDenial(approval), causedBy);\n }\n }\n\n if (highRisk === 'outbound' && this.#leakInspector) {\n // #727 M3.4: messages to other conversations go through the same leak check as replies.\n effectiveInput = this.#leakInspector.inspectArguments(effectiveInput).input;\n }\n\n const startedAt = Date.now();\n try {\n const output = await tool.execute(effectiveInput, toolInvocationFromTurn(this.turn));\n this.#observeOutput(tool, effectiveInput, output);\n const durationMs = Date.now() - startedAt;\n // A tool that ignores cancellation may already have committed side effects.\n // Record its real settlement so the audit trail never invites an unsafe retry.\n await this.#append({ type: 'tool_result', toolName: name, output, durationMs, toolUseId }, causedBy);\n if (this.turn.signal.aborted) return this.#cancelledOutcome();\n return Object.freeze({ status: 'completed', output, durationMs });\n } catch (error) {\n if (error instanceof TurnJournalCommitError) throw error;\n const durationMs = Date.now() - startedAt;\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, durationMs, causedBy);\n if (error instanceof NetworkAccessDeniedError) {\n return this.#deny(name, toolUseId, error.policy, error.message, causedBy);\n }\n const message = error instanceof Error ? error.message : String(error);\n await this.#append({ type: 'tool_failed', toolName: name, toolUseId, error: message, durationMs }, causedBy);\n return Object.freeze({ status: 'failed', error: message, retryable: false });\n }\n }\n\n /** Records where the result came from, and which parts are another session's memory. */\n #observeOutput(tool: ToolCapability, input: Readonly<Record<string, unknown>>, output: unknown): void {\n if (isExternalContentSource(tool, this.#guard)) {\n this.provenance.record(tool.name === 'spawn_task'\n ? { kind: 'subagent_report' }\n : { kind: 'tool_result', toolName: tool.name });\n }\n if (this.#leakInspector && readsOtherSessionMemory(input, this.turn.session.key)) {\n this.#leakInspector.protectOtherSessionMemory(collectText(output));\n }\n }\n\n async #deny(toolName: string, toolUseId: string, policy: string, reason: string, causedBy?: ToolExecutionCause): Promise<TurnToolOutcome> {\n await this.#append({ type: 'tool_denied', toolName, toolUseId, policy, reason }, causedBy);\n return Object.freeze({ status: 'denied', policy, reason });\n }\n\n async #cancel(toolName: string, toolUseId: string, durationMs: number, causedBy?: ToolExecutionCause): Promise<TurnToolOutcome> {\n const outcome = this.#cancelledOutcome();\n await this.#append({ type: 'tool_cancelled', toolName, toolUseId, reason: outcome.reason, durationMs }, causedBy);\n return outcome;\n }\n\n #cancelledOutcome(): Extract<TurnToolOutcome, { status: 'cancelled' }> {\n const reason = this.turn.signal.reason instanceof Error\n ? this.turn.signal.reason.message\n : String(this.turn.signal.reason ?? 'turn cancelled');\n return Object.freeze({ status: 'cancelled', reason });\n }\n\n async #append(event: ToolJournalEvent, causedBy?: ToolExecutionCause): Promise<void> {\n try {\n await this.turn.ports.journal.append(causedBy ? { ...event, causedBy } : event);\n } catch (error) {\n throw new TurnJournalCommitError(error);\n }\n }\n}\n\nfunction formatApprovalQuestion(\n toolName: string,\n reason: string,\n input: Readonly<Record<string, unknown>>,\n): string {\n const command = typeof input.command === 'string' ? input.command.trim() : '';\n const cwd = typeof input.cwd === 'string' ? input.cwd.trim() : '';\n const path = ['file_path', 'path'].map((key) => input[key]).find((value) => typeof value === 'string') as string | undefined;\n return [\n `工具「${toolName}」需要确认后执行。`,\n command ? `命令:${command}` : undefined,\n cwd ? `工作目录:${cwd}` : undefined,\n path ? `目标文件:${path}` : undefined,\n `风险:${reason}`,\n '是否继续?',\n ].filter((line): line is string => Boolean(line)).join('\\n');\n}\n\nconst SESSION_MEMORY_PATH = /(?:^|[\\\\/])data[\\\\/]memory[\\\\/]sessions[\\\\/]([^\\\\/]+)/;\n\n/** True when a file tool's path points into a different session's memory directory. */\nfunction readsOtherSessionMemory(input: Readonly<Record<string, unknown>>, sessionKey: string): boolean {\n const path = ['file_path', 'path', 'filePath'].map((key) => input[key]).find((value) => typeof value === 'string') as string | undefined;\n const match = path ? SESSION_MEMORY_PATH.exec(path) : null;\n return match !== null && match[1] !== safeSessionKey(sessionKey);\n}\n\n/** Every string inside a tool output, one per line (file tools may return structured results). */\nfunction collectText(value: unknown, depth = 0): string {\n if (typeof value === 'string') return value;\n if (depth > 4 || value === null || typeof value !== 'object') return '';\n const entries = Array.isArray(value) ? value : Object.values(value as Record<string, unknown>);\n return entries.map((entry) => collectText(entry, depth + 1)).filter(Boolean).join('\\n');\n}\n\nfunction approvalScopeKey(toolName: string, input: Readonly<Record<string, unknown>>): string {\n return `${toolName}:${stableApprovalValue(input)}`;\n}\n\nfunction stableApprovalValue(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableApprovalValue).join(',')}]`;\n if (value && typeof value === 'object') {\n return `{${Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, entry]) => `${JSON.stringify(key)}:${stableApprovalValue(entry)}`)\n .join(',')}}`;\n }\n if (typeof value === 'undefined') return 'undefined';\n try { return JSON.stringify(value); } catch { return String(value); }\n}\n\n/** Adapts canonical capability execution to the full AgentCore seam. */\nexport class TurnToolExecutionAuthority implements ToolExecutionAuthority {\n readonly #runtime: TurnToolRuntime;\n\n constructor(runtime: TurnToolRuntime) {\n this.#runtime = runtime;\n }\n\n execute(\n tool: AgentTool,\n input: Readonly<Record<string, unknown>>,\n toolUseId: string,\n cause?: ToolExecutionCause,\n ) {\n return this.#runtime.execute(tool.name, input, toolUseId, cause);\n }\n}\n\nfunction freezeToolExecutionCause(cause?: ToolExecutionCause): ToolExecutionCause | undefined {\n if (!cause?.principal && !cause?.turn) return undefined;\n return Object.freeze({\n ...(cause.principal ? {\n principal: Object.freeze({\n ...cause.principal,\n ...(cause.principal.roles ? { roles: Object.freeze([...cause.principal.roles]) } : {}),\n }),\n } : {}),\n ...(cause.turn ? { turn: Object.freeze({ ...cause.turn }) } : {}),\n });\n}\n"]}
1
+ {"version":3,"file":"turn-tool-runtime.js","sourceRoot":"","sources":["../../src/tool/turn-tool-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAE/E,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EACL,iBAAiB,EACjB,4BAA4B,EAC5B,cAAc,EACd,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,GAEtB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAqBrD,kFAAkF;AAClF,MAAM,OAAO,eAAe;IAQP;IAPV,MAAM,CAAsC;IAC5C,MAAM,CAAyB;IAC/B,cAAc,CAAyB;IAChD,0EAA0E;IACjE,UAAU,CAAiB;IAEpC,YACmB,IAAiB,EAClC,KAAgC,EAChC,UAAkC,EAAE;QAFnB,SAAI,GAAJ,IAAI,CAAa;QAIlC,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QAC9D,IAAI,CAAC,MAAM,GAAG,qBAAqB,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAC5D,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,IAAI,cAAc,EAAE,CAAC;QAC7D,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC;IACnG,CAAC;IAED,KAAK,CAAC,OAAO,CACX,IAAY,EACZ,KAAwC,EACxC,SAAiB,EACjB,KAA0B;QAE1B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,QAAQ,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAC;QACjD,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,4BAA4B,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC1G,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,QAAQ,CAAC,CAAC;QACnG,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAChF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7E,wEAAwE;QACxE,sEAAsE;QACtE,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAChF,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACjF,CAAC;QACD,IAAI,cAAc,GAAG,QAAQ,CAAC,KAAK,CAAC;QACpC,MAAM,QAAQ,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;QAC9C,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,KAAK,OAAO,CAAC;QACpD,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,CAAC;gBACH,sFAAsF;gBACtF,cAAc,GAAG,eAAe,CAAC,cAAc,CAAC,CAAC;gBACjD,wFAAwF;gBACxF,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;gBAC/B,IAAI,QAAQ,KAAK,UAAU,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;oBACnD,8EAA8E;oBAC9E,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC,KAAK,CAAC;gBAC9E,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,4DAA4D,EAAE,QAAQ,CAAC,CAAC;YACzH,CAAC;QACH,CAAC;QACD,MAAM,cAAc,GAAG,QAAQ,KAAK,SAAS;eACxC,IAAI,CAAC,MAAM,CAAC,eAAe,KAAK,kBAAkB;eAClD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAC7B,IAAI,cAAc,EAAE,CAAC;YACnB,sEAAsE;YACtE,kEAAkE;YAClE,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACtF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,iBAAiB,EAAE,4BAA4B,EAAE,QAAQ,CAAC,CAAC;YAChG,CAAC;YACD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;YACpF,MAAM,QAAQ,GAAG,MAAM,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE;gBACtE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzC,SAAS,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,SAAS,EAAE;gBACtD,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG;gBACjC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI;oBAChC,CAAC,CAAC,EAAE,iBAAiB,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;oBAC/C,CAAC,CAAC,EAAE,CAAC;gBACP,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS;gBAC1C,QAAQ,EAAE,IAAI;gBACd,kFAAkF;gBAClF,QAAQ,EAAE,GAAG,iBAAiB,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,SAAS,EAAE;gBAC1E,QAAQ,EAAE,sBAAsB,CAAC,IAAI,EAAE,sBAAsB,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,cAAc,EAC5F,SAAS,CAAC;gBACZ,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM;aACzB,CAAC,CAAC;YACH,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;YAChF,IAAI,QAAQ,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBACnC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,iBAAiB,EAAE,oBAAoB,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;YAClG,CAAC;QACH,CAAC;aAAM,IAAI,QAAQ,CAAC,MAAM,KAAK,mBAAmB,EAAE,CAAC;YACnD,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACtF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,MAAM,EAAE,gDAAgD,EAAE,QAAQ,CAAC,CAAC;YAClH,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE;gBACtE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzC,SAAS,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,SAAS,EAAE;gBACtD,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG;gBACjC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI;oBAChC,CAAC,CAAC,EAAE,iBAAiB,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;oBAC/C,CAAC,CAAC,EAAE,CAAC;gBACP,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS;gBAC1C,QAAQ,EAAE,IAAI;gBACd,QAAQ,EAAE,IAAI,CAAC,gBAAgB,KAAK,MAAM;oBACxC,CAAC,CAAC,IAAI;oBACN,CAAC,CAAC,gBAAgB,CAAC,IAAI,EAAE,cAAc,CAAC;gBAC1C,GAAG,CAAC,IAAI,CAAC,gBAAgB,KAAK,MAAM,IAAI,QAAQ,CAAC,MAAM,KAAK,aAAa;oBACvE,CAAC,CAAC,EAAE,QAAQ,EAAE,SAAkB,EAAE;oBAClC,CAAC,CAAC,EAAE,CAAC;gBACP,QAAQ,EAAE,sBAAsB,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,EAAE,cAAc,EAAE,SAAS,CAAC;gBAClF,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM;aACzB,CAAC,CAAC;YACH,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;YAChF,IAAI,QAAQ,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBACnC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;YAChG,CAAC;QACH,CAAC;QAED,IAAI,CAAC,SAAS,IAAI,QAAQ,KAAK,UAAU,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACjE,wFAAwF;YACxF,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC,KAAK,CAAC;QAC9E,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YACrF,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;YAClD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAC1C,4EAA4E;YAC5E,+EAA+E;YAC/E,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,QAAQ,CAAC,CAAC;YACrG,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC9D,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;QACpE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,sBAAsB;gBAAE,MAAM,KAAK,CAAC;YACzD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAC1C,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;YACzF,IAAI,KAAK,YAAY,wBAAwB,EAAE,CAAC;gBAC9C,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC5E,CAAC;YACD,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,QAAQ,CAAC,CAAC;YAC7G,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAED,wFAAwF;IACxF,cAAc,CAAC,IAAoB,EAAE,KAAwC,EAAE,MAAe;QAC5F,IAAI,uBAAuB,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/C,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,YAAY;gBAC/C,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE;gBAC7B,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACpD,CAAC;QACD,IAAI,IAAI,CAAC,cAAc,IAAI,uBAAuB,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACjF,IAAI,CAAC,cAAc,CAAC,yBAAyB,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,QAAgB,EAAE,SAAiB,EAAE,MAAc,EAAE,MAAc,EAAE,QAA6B;QAC5G,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC3F,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,QAAgB,EAAE,SAAiB,EAAE,UAAkB,EAAE,QAA6B;QAClG,MAAM,OAAO,GAAG,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzC,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,QAAQ,CAAC,CAAC;QAClH,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,iBAAiB;QACf,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,YAAY,KAAK;YACrD,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO;YACjC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,gBAAgB,CAAC,CAAC;QACxD,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,KAAuB,EAAE,QAA6B;QAClE,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAClF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,sBAAsB,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;CACF;AAED,SAAS,sBAAsB,CAC7B,QAAgB,EAChB,MAAc,EACd,KAAwC,EACxC,SAAkB;IAElB,MAAM,OAAO,GAAG,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9E,MAAM,GAAG,GAAG,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAClE,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAuB,CAAC;IAC7H,OAAO;QACL,MAAM,QAAQ,WAAW;QACzB,OAAO,CAAC,CAAC,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS;QACrC,GAAG,CAAC,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS;QAC/B,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS;QACjC,SAAS,CAAC,CAAC,CAAC,QAAQ,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;QAC5D,MAAM,MAAM,EAAE;QACd,OAAO;KACR,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,mBAAmB,GAAG,uDAAuD,CAAC;AAEpF,uFAAuF;AACvF,SAAS,uBAAuB,CAAC,KAAwC,EAAE,UAAkB;IAC3F,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAuB,CAAC;IACzI,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3D,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,cAAc,CAAC,UAAU,CAAC,CAAC;AACnE,CAAC;AAED,kGAAkG;AAClG,SAAS,WAAW,CAAC,KAAc,EAAE,KAAK,GAAG,CAAC;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACxE,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAgC,CAAC,CAAC;IAC/F,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1F,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAgB,EAAE,KAAwC;IAClF,OAAO,GAAG,QAAQ,IAAI,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC;AACrD,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAc;IACzC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IACjF,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC;aACxD,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;aACpD,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC;aAC7E,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAClB,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,WAAW;QAAE,OAAO,WAAW,CAAC;IACrD,IAAI,CAAC;QAAC,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAAC,CAAC;AACvE,CAAC;AAED,wEAAwE;AACxE,MAAM,OAAO,0BAA0B;IAC5B,QAAQ,CAAkB;IAEnC,YAAY,OAAwB;QAClC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,OAAO,CACL,IAAe,EACf,KAAwC,EACxC,SAAiB,EACjB,KAA0B;QAE1B,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IACnE,CAAC;CACF;AAED,SAAS,wBAAwB,CAAC,KAA0B;IAC1D,IAAI,CAAC,KAAK,EAAE,SAAS,IAAI,CAAC,KAAK,EAAE,IAAI;QAAE,OAAO,SAAS,CAAC;IACxD,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;YACpB,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC;gBACvB,GAAG,KAAK,CAAC,SAAS;gBAClB,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACvF,CAAC;SACH,CAAC,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClE,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { runTurnToolPolicies } from '../security/policy-facade.js';\nimport type { ToolCapability } from '../plugin-runtime/capability-ingress.js';\nimport { toolInvocationFromTurn } from '../plugin-runtime/capability-tools.js';\nimport type { TurnIngress } from '../turn/turn-ingress.js';\nimport {\n formatApprovalDenial,\n isApprovalPortAvailable,\n requestApprovalOutcome,\n} from '../session/approval-port.js';\nimport type { AgentTool, ToolExecutionCause } from '@zhin.js/ai';\nimport type { ToolExecutionAuthority } from '../core/tool-execution-authority.js';\nimport { TurnJournalCommitError } from '../turn/journal-integrity.js';\nimport { NetworkAccessDeniedError } from '../security/network-policy.js';\nimport {\n PROVENANCE_POLICY,\n PROVENANCE_UNATTENDED_REASON,\n TurnProvenance,\n classifyHighRiskAction,\n formatProvenanceReason,\n isExternalContentSource,\n resolveInjectionGuard,\n type InjectionGuardSettings,\n} from '../security/provenance.js';\nimport type { OutboundLeakInspector } from '../security/outbound-leak-guard.js';\nimport { resolveTurnTrustLevel } from '../security/trust-level.js';\nimport { safeSessionKey } from '../memory-layers.js';\n\ntype ToolJournalEvent = Extract<import('../event/turn-event.js').TurnEvent, {\n type: 'tool_call' | 'tool_result' | 'tool_denied' | 'tool_failed' | 'tool_cancelled';\n}>;\n\nexport type TurnToolOutcome =\n | Readonly<{ status: 'completed'; output: unknown; durationMs: number }>\n | Readonly<{ status: 'denied'; policy: string; reason: string }>\n | Readonly<{ status: 'cancelled'; reason: string }>\n | Readonly<{ status: 'failed'; error: string; retryable: boolean }>;\n\nexport interface TurnToolRuntimeOptions {\n /** `ai.agent.injectionGuard`; unset keys use the conservative defaults. */\n readonly injectionGuard?: Partial<InjectionGuardSettings>;\n /** Shared with the engine so sources outside tool calls (background events) also count. */\n readonly provenance?: TurnProvenance;\n /** Masks leaks in the text arguments of outbound tools and learns other-session memory reads. */\n readonly leakInspector?: OutboundLeakInspector;\n}\n\n/** Sole execution authority for Tool capabilities within one fixed Turn lease. */\nexport class TurnToolRuntime {\n readonly #tools: ReadonlyMap<string, ToolCapability>;\n readonly #guard: InjectionGuardSettings;\n readonly #leakInspector?: OutboundLeakInspector;\n /** Lower-trust sources that reached the model during this turn (#727). */\n readonly provenance: TurnProvenance;\n\n constructor(\n private readonly turn: TurnIngress,\n tools: readonly ToolCapability[],\n options: TurnToolRuntimeOptions = {},\n ) {\n this.#tools = new Map(tools.map((tool) => [tool.name, tool]));\n this.#guard = resolveInjectionGuard(options.injectionGuard);\n this.provenance = options.provenance ?? new TurnProvenance();\n this.#leakInspector = this.#guard.outboundCheck === 'redact' ? options.leakInspector : undefined;\n }\n\n async execute(\n name: string,\n input: Readonly<Record<string, unknown>>,\n toolUseId: string,\n cause?: ToolExecutionCause,\n ): Promise<TurnToolOutcome> {\n const tool = this.#tools.get(name);\n const causedBy = freezeToolExecutionCause(cause);\n if (!tool) return this.#deny(name, toolUseId, 'capability', `Unknown Tool capability: ${name}`, causedBy);\n await this.#append({ type: 'tool_call', toolName: name, args: { ...input }, toolUseId }, causedBy);\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n const decision = await runTurnToolPolicies({ turn: this.turn, tool, input });\n // File/cwd authorization is asynchronous: cancellation can arrive after\n // the ingress check but before an allowed decision reaches execution.\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n if (decision.status === 'denied') {\n return this.#deny(name, toolUseId, decision.policy, decision.reason, causedBy);\n }\n let effectiveInput = decision.input;\n const highRisk = classifyHighRiskAction(tool);\n const humanOnly = tool.requiresApproval === 'human';\n if (humanOnly) {\n try {\n // Caller-owned nested values must not change the operation while a person reviews it.\n effectiveInput = structuredClone(effectiveInput);\n // Reject cycles and other non-serializable input before constructing the approval card.\n JSON.stringify(effectiveInput);\n if (highRisk === 'outbound' && this.#leakInspector) {\n // Show exactly what will execute; do not redact approved arguments afterward.\n effectiveInput = this.#leakInspector.inspectArguments(effectiveInput).input;\n }\n } catch {\n return this.#deny(name, toolUseId, 'approval', 'human approval requires an isolated JSON argument snapshot', causedBy);\n }\n }\n const provenanceGate = highRisk !== undefined\n && this.#guard.confirmHighRisk === 'external_content'\n && this.provenance.tainted;\n if (provenanceGate) {\n // #727 M3.3: a high-risk action after lower-trust content always asks\n // first, even when the configured policy would allow it outright.\n if (this.turn.policy.unattended || !isApprovalPortAvailable(this.turn.ports.approval)) {\n return this.#deny(name, toolUseId, PROVENANCE_POLICY, PROVENANCE_UNATTENDED_REASON, causedBy);\n }\n const chain = this.provenance.describeChain(resolveTurnTrustLevel(this.turn), name);\n const approval = await requestApprovalOutcome(this.turn.ports.approval, {\n ...(humanOnly ? { humanOnly: true } : {}),\n requestId: `${this.turn.identity.turnId}:${toolUseId}`,\n sessionKey: this.turn.session.key,\n ...(this.turn.origin.kind === 'im'\n ? { conversationScope: this.turn.origin.scope }\n : {}),\n requesterId: this.turn.principal.subjectId,\n toolName: name,\n // Never matches a remembered approval: each tainted call is confirmed on its own.\n scopeKey: `${PROVENANCE_POLICY}:${this.turn.identity.turnId}:${toolUseId}`,\n question: formatApprovalQuestion(name, formatProvenanceReason(highRisk, chain), effectiveInput,\n humanOnly),\n signal: this.turn.signal,\n });\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n if (approval.status !== 'approved') {\n return this.#deny(name, toolUseId, PROVENANCE_POLICY, formatApprovalDenial(approval), causedBy);\n }\n } else if (decision.status === 'approval_required') {\n if (this.turn.policy.unattended || !isApprovalPortAvailable(this.turn.ports.approval)) {\n return this.#deny(name, toolUseId, decision.policy, 'approval required but ApprovalPort unavailable', causedBy);\n }\n const approval = await requestApprovalOutcome(this.turn.ports.approval, {\n ...(humanOnly ? { humanOnly: true } : {}),\n requestId: `${this.turn.identity.turnId}:${toolUseId}`,\n sessionKey: this.turn.session.key,\n ...(this.turn.origin.kind === 'im'\n ? { conversationScope: this.turn.origin.scope }\n : {}),\n requesterId: this.turn.principal.subjectId,\n toolName: name,\n scopeKey: tool.requiresApproval === 'once'\n ? name\n : approvalScopeKey(name, effectiveInput),\n ...(tool.requiresApproval === 'once' || decision.policy === 'exec-policy'\n ? { remember: 'session' as const }\n : {}),\n question: formatApprovalQuestion(name, decision.reason, effectiveInput, humanOnly),\n signal: this.turn.signal,\n });\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n if (approval.status !== 'approved') {\n return this.#deny(name, toolUseId, decision.policy, formatApprovalDenial(approval), causedBy);\n }\n }\n\n if (!humanOnly && highRisk === 'outbound' && this.#leakInspector) {\n // #727 M3.4: messages to other conversations go through the same leak check as replies.\n effectiveInput = this.#leakInspector.inspectArguments(effectiveInput).input;\n }\n\n const startedAt = Date.now();\n try {\n const output = await tool.execute(effectiveInput, toolInvocationFromTurn(this.turn));\n this.#observeOutput(tool, effectiveInput, output);\n const durationMs = Date.now() - startedAt;\n // A tool that ignores cancellation may already have committed side effects.\n // Record its real settlement so the audit trail never invites an unsafe retry.\n await this.#append({ type: 'tool_result', toolName: name, output, durationMs, toolUseId }, causedBy);\n if (this.turn.signal.aborted) return this.#cancelledOutcome();\n return Object.freeze({ status: 'completed', output, durationMs });\n } catch (error) {\n if (error instanceof TurnJournalCommitError) throw error;\n const durationMs = Date.now() - startedAt;\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, durationMs, causedBy);\n if (error instanceof NetworkAccessDeniedError) {\n return this.#deny(name, toolUseId, error.policy, error.message, causedBy);\n }\n const message = error instanceof Error ? error.message : String(error);\n await this.#append({ type: 'tool_failed', toolName: name, toolUseId, error: message, durationMs }, causedBy);\n return Object.freeze({ status: 'failed', error: message, retryable: false });\n }\n }\n\n /** Records where the result came from, and which parts are another session's memory. */\n #observeOutput(tool: ToolCapability, input: Readonly<Record<string, unknown>>, output: unknown): void {\n if (isExternalContentSource(tool, this.#guard)) {\n this.provenance.record(tool.name === 'spawn_task'\n ? { kind: 'subagent_report' }\n : { kind: 'tool_result', toolName: tool.name });\n }\n if (this.#leakInspector && readsOtherSessionMemory(input, this.turn.session.key)) {\n this.#leakInspector.protectOtherSessionMemory(collectText(output));\n }\n }\n\n async #deny(toolName: string, toolUseId: string, policy: string, reason: string, causedBy?: ToolExecutionCause): Promise<TurnToolOutcome> {\n await this.#append({ type: 'tool_denied', toolName, toolUseId, policy, reason }, causedBy);\n return Object.freeze({ status: 'denied', policy, reason });\n }\n\n async #cancel(toolName: string, toolUseId: string, durationMs: number, causedBy?: ToolExecutionCause): Promise<TurnToolOutcome> {\n const outcome = this.#cancelledOutcome();\n await this.#append({ type: 'tool_cancelled', toolName, toolUseId, reason: outcome.reason, durationMs }, causedBy);\n return outcome;\n }\n\n #cancelledOutcome(): Extract<TurnToolOutcome, { status: 'cancelled' }> {\n const reason = this.turn.signal.reason instanceof Error\n ? this.turn.signal.reason.message\n : String(this.turn.signal.reason ?? 'turn cancelled');\n return Object.freeze({ status: 'cancelled', reason });\n }\n\n async #append(event: ToolJournalEvent, causedBy?: ToolExecutionCause): Promise<void> {\n try {\n await this.turn.ports.journal.append(causedBy ? { ...event, causedBy } : event);\n } catch (error) {\n throw new TurnJournalCommitError(error);\n }\n }\n}\n\nfunction formatApprovalQuestion(\n toolName: string,\n reason: string,\n input: Readonly<Record<string, unknown>>,\n humanOnly: boolean,\n): string {\n const command = typeof input.command === 'string' ? input.command.trim() : '';\n const cwd = typeof input.cwd === 'string' ? input.cwd.trim() : '';\n const path = ['file_path', 'path'].map((key) => input[key]).find((value) => typeof value === 'string') as string | undefined;\n return [\n `工具「${toolName}」需要确认后执行。`,\n command ? `命令:${command}` : undefined,\n cwd ? `工作目录:${cwd}` : undefined,\n path ? `目标文件:${path}` : undefined,\n humanOnly ? `本次参数:${stableApprovalValue(input)}` : undefined,\n `风险:${reason}`,\n '是否继续?',\n ].filter((line): line is string => Boolean(line)).join('\\n');\n}\n\nconst SESSION_MEMORY_PATH = /(?:^|[\\\\/])data[\\\\/]memory[\\\\/]sessions[\\\\/]([^\\\\/]+)/;\n\n/** True when a file tool's path points into a different session's memory directory. */\nfunction readsOtherSessionMemory(input: Readonly<Record<string, unknown>>, sessionKey: string): boolean {\n const path = ['file_path', 'path', 'filePath'].map((key) => input[key]).find((value) => typeof value === 'string') as string | undefined;\n const match = path ? SESSION_MEMORY_PATH.exec(path) : null;\n return match !== null && match[1] !== safeSessionKey(sessionKey);\n}\n\n/** Every string inside a tool output, one per line (file tools may return structured results). */\nfunction collectText(value: unknown, depth = 0): string {\n if (typeof value === 'string') return value;\n if (depth > 4 || value === null || typeof value !== 'object') return '';\n const entries = Array.isArray(value) ? value : Object.values(value as Record<string, unknown>);\n return entries.map((entry) => collectText(entry, depth + 1)).filter(Boolean).join('\\n');\n}\n\nfunction approvalScopeKey(toolName: string, input: Readonly<Record<string, unknown>>): string {\n return `${toolName}:${stableApprovalValue(input)}`;\n}\n\nfunction stableApprovalValue(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableApprovalValue).join(',')}]`;\n if (value && typeof value === 'object') {\n return `{${Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, entry]) => `${JSON.stringify(key)}:${stableApprovalValue(entry)}`)\n .join(',')}}`;\n }\n if (typeof value === 'undefined') return 'undefined';\n try { return JSON.stringify(value); } catch { return String(value); }\n}\n\n/** Adapts canonical capability execution to the full AgentCore seam. */\nexport class TurnToolExecutionAuthority implements ToolExecutionAuthority {\n readonly #runtime: TurnToolRuntime;\n\n constructor(runtime: TurnToolRuntime) {\n this.#runtime = runtime;\n }\n\n execute(\n tool: AgentTool,\n input: Readonly<Record<string, unknown>>,\n toolUseId: string,\n cause?: ToolExecutionCause,\n ) {\n return this.#runtime.execute(tool.name, input, toolUseId, cause);\n }\n}\n\nfunction freezeToolExecutionCause(cause?: ToolExecutionCause): ToolExecutionCause | undefined {\n if (!cause?.principal && !cause?.turn) return undefined;\n return Object.freeze({\n ...(cause.principal ? {\n principal: Object.freeze({\n ...cause.principal,\n ...(cause.principal.roles ? { roles: Object.freeze([...cause.principal.roles]) } : {}),\n }),\n } : {}),\n ...(cause.turn ? { turn: Object.freeze({ ...cause.turn }) } : {}),\n });\n}\n"]}
@@ -22,10 +22,7 @@ function formatDiscoverResults(items, query) {
22
22
  return items.map(item => `- [${item.kind}] ${item.name}: ${item.brief}`).join('\n');
23
23
  }
24
24
  function schemaSummary(item) {
25
- const params = item.fullTool.parameters;
26
- if (!params)
27
- return `Loaded tool "${item.name}".`;
28
- return `Loaded tool "${item.name}".\nParameters schema:\n${JSON.stringify(params, null, 2)}`;
25
+ return `Loaded tool "${item.name}". Use its tool definition for arguments.`;
29
26
  }
30
27
  export function createDeferredTurnController(options) {
31
28
  let currentSnapshot = structuredClone(options.snapshot);