|
9 | 9 | "github.com/docker/docker-agent/pkg/hooks" |
10 | 10 | "github.com/docker/docker-agent/pkg/hooks/builtins" |
11 | 11 | "github.com/docker/docker-agent/pkg/session" |
| 12 | + "github.com/docker/docker-agent/pkg/tools" |
12 | 13 | ) |
13 | 14 |
|
14 | 15 | // buildHooksExecutors builds a [hooks.Executor] for every agent in the |
@@ -177,6 +178,84 @@ func (r *LocalRuntime) notify(ctx context.Context, a *agent.Agent, event hooks.E |
177 | 178 | }, nil) |
178 | 179 | } |
179 | 180 |
|
| 181 | +// Agent-switch kinds passed via [hooks.Input.AgentSwitchKind] to |
| 182 | +// describe what kind of transition triggered the on_agent_switch |
| 183 | +// event. Constants instead of literals so the hook contract is |
| 184 | +// discoverable from the runtime side and a typo trips a compile |
| 185 | +// error. |
| 186 | +const ( |
| 187 | + agentSwitchKindTransferTask = "transfer_task" |
| 188 | + agentSwitchKindTransferTaskReturn = "transfer_task_return" |
| 189 | + agentSwitchKindHandoff = "handoff" |
| 190 | +) |
| 191 | + |
| 192 | +// executeOnAgentSwitchHooks fires on_agent_switch when the runtime |
| 193 | +// changes the active agent. Observational; failures are logged. The |
| 194 | +// hook runs alongside the existing [AgentSwitching] event, so users |
| 195 | +// who already consume that event see no behaviour change. |
| 196 | +func (r *LocalRuntime) executeOnAgentSwitchHooks(ctx context.Context, a *agent.Agent, sessionID, fromAgent, toAgent, kind string) { |
| 197 | + r.dispatchHook(ctx, a, hooks.EventOnAgentSwitch, &hooks.Input{ |
| 198 | + SessionID: sessionID, |
| 199 | + FromAgent: fromAgent, |
| 200 | + ToAgent: toAgent, |
| 201 | + AgentSwitchKind: kind, |
| 202 | + }, nil) |
| 203 | +} |
| 204 | + |
| 205 | +// executeOnSessionResumeHooks fires on_session_resume when the user |
| 206 | +// explicitly approves continuation past the configured |
| 207 | +// max_iterations limit. Observational; failures are logged. The hook |
| 208 | +// runs alongside the existing event-channel signalling so audit / |
| 209 | +// quota / alerting pipelines can react without subscribing to the |
| 210 | +// per-session channel. |
| 211 | +func (r *LocalRuntime) executeOnSessionResumeHooks(ctx context.Context, a *agent.Agent, sessionID string, prevMax, newMax int) { |
| 212 | + r.dispatchHook(ctx, a, hooks.EventOnSessionResume, &hooks.Input{ |
| 213 | + SessionID: sessionID, |
| 214 | + PreviousMaxIterations: prevMax, |
| 215 | + NewMaxIterations: newMax, |
| 216 | + }, nil) |
| 217 | +} |
| 218 | + |
| 219 | +// Verdicts and sources for [hooks.EventOnToolApprovalDecision]. Constants |
| 220 | +// instead of literals so the contract between executeWithApproval and |
| 221 | +// the hook protocol is discoverable from the runtime side and a typo |
| 222 | +// trips a compile error. |
| 223 | +const ( |
| 224 | + ApprovalDecisionAllow = "allow" |
| 225 | + ApprovalDecisionDeny = "deny" |
| 226 | + ApprovalDecisionCanceled = "canceled" |
| 227 | + |
| 228 | + ApprovalSourceYolo = "yolo" |
| 229 | + ApprovalSourceSessionPermissionsAllow = "session_permissions_allow" |
| 230 | + ApprovalSourceSessionPermissionsDeny = "session_permissions_deny" |
| 231 | + ApprovalSourceTeamPermissionsAllow = "team_permissions_allow" |
| 232 | + ApprovalSourceTeamPermissionsDeny = "team_permissions_deny" |
| 233 | + ApprovalSourceReadOnlyHint = "readonly_hint" |
| 234 | + ApprovalSourceUserApproved = "user_approved" |
| 235 | + ApprovalSourceUserApprovedSession = "user_approved_session" |
| 236 | + ApprovalSourceUserApprovedTool = "user_approved_tool" |
| 237 | + ApprovalSourceUserRejected = "user_rejected" |
| 238 | + ApprovalSourceContextCanceled = "context_canceled" |
| 239 | +) |
| 240 | + |
| 241 | +// executeOnToolApprovalDecisionHooks fires on_tool_approval_decision |
| 242 | +// after the runtime's approval chain has resolved a verdict for a |
| 243 | +// tool call. Fired once per call from each return path of |
| 244 | +// [executeWithApproval], so a single hook gets one record per tool |
| 245 | +// call regardless of which step decided. |
| 246 | +func (r *LocalRuntime) executeOnToolApprovalDecisionHooks( |
| 247 | + ctx context.Context, |
| 248 | + sess *session.Session, |
| 249 | + a *agent.Agent, |
| 250 | + toolCall tools.ToolCall, |
| 251 | + decision, source string, |
| 252 | +) { |
| 253 | + input := newHooksInput(sess, toolCall) |
| 254 | + input.ApprovalDecision = decision |
| 255 | + input.ApprovalSource = source |
| 256 | + r.dispatchHook(ctx, a, hooks.EventOnToolApprovalDecision, input, nil) |
| 257 | +} |
| 258 | + |
180 | 259 | // executeBeforeLLMCallHooks fires before_llm_call just before each |
181 | 260 | // model call. A terminating verdict (decision="block" / continue=false |
182 | 261 | // / exit 2) stops the run loop — see [hooks.EventBeforeLLMCall] for |
|
0 commit comments