返回 DeepSeek-Reasonix
tool_write_coordination.go
根目录 / internal / agent / tool_write_coordination.go
1 package agent
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 )
8
9 // prepareWriteCoordination resolves the real execution target, then acquires
10 // every write guard that must cover hooks, checkpoints, and Execute.
11 func (a *Agent) prepareWriteCoordination(ctx context.Context, plan *toolCallPlan) (toolOutcome, bool) {
12 plan.runTool = plan.execTool
13 plan.runArgs = plan.execArgs
14 plan.hooksMayMutateWorkspace = toolHooksMayMutateWorkspace(a.svc.hooks)
15 if plan.resolved.Target != nil {
16 plan.runTool = plan.resolved.Target
17 plan.runArgs = plan.resolved.Args
18 if len(plan.runArgs) == 0 {
19 plan.runArgs = json.RawMessage(`{}`)
20 }
21 }
22 if (plan.effects.WorkspaceMutation || plan.hooksMayMutateWorkspace) && a.svc.workspaceLease != nil {
23 release, err := a.acquireWorkspaceLease(ctx, plan)
24 if err != nil {
25 return toolOutcome{
26 output: fmt.Sprintf("blocked: the workspace did not become available for writing: %v", err),
27 blocked: true, errMsg: "blocked: workspace write lease unavailable",
28 }, true
29 }
30 plan.releaseLease = release
31 }
32 release, claimID, err := a.reserveCoordinatedParentWrite(plan)
33 if err != nil {
34 return writeClaimBlockedOutcome(err), true
35 }
36 plan.releaseParentWrite = release
37 plan.parentWriteClaimID = claimID
38 return a.applyLiveWriteReservation(ctx, plan)
39 }
40
41 // reserveCoordinatedParentWrite also returns the hook claim's id: that claim
42 // covers delegation tools, whose subagents run inside it.
43 func (a *Agent) reserveCoordinatedParentWrite(plan *toolCallPlan) (func(), int64, error) {
44 if plan.hooksMayMutateWorkspace &&
45 a.svc.writeScheduler != nil && a.subagentDepth == 0 {
46 claim, err := WholeWorkspaceWriteClaim(a.writeWorkspaceRoot)
47 if err != nil {
48 return func() {}, 0, err
49 }
50 return a.svc.writeScheduler.ReserveParentWriteWithID(claim)
51 }
52 release, err := a.reserveParentWrite(plan.runTool, plan.runArgs, !plan.effects.WorkspaceMutation)
53 return release, 0, err
54 }
55
56 func (a *Agent) acquireWorkspaceLease(ctx context.Context, plan *toolCallPlan) (func(), error) {
57 noop := func() {}
58 if a == nil || a.svc.workspaceLease == nil || plan == nil || plan.runTool == nil {
59 return noop, nil
60 }
61 // Tool hooks are arbitrary user shell code, so their write surface cannot be
62 // narrowed to the concrete tool's path arguments.
63 if plan.hooksMayMutateWorkspace {
64 return a.svc.workspaceLease.HoldWrite(ctx)
65 }
66 name := plan.runTool.Name()
67 if pathBoundWriterNames[name] {
68 paths, err := extractWritePathsFromArgs(name, a.writeWorkspaceRoot, plan.runArgs)
69 if err == nil && len(paths) > 0 {
70 for i := range paths {
71 paths[i] = resolveMaybeRelative(a.writeWorkspaceRoot, paths[i])
72 }
73 return a.svc.workspaceLease.HoldWriteForPaths(ctx, paths)
74 }
75 }
76 return a.svc.workspaceLease.HoldWrite(ctx)
77 }
78
79 func (a *Agent) applyLiveWriteReservation(ctx context.Context, plan *toolCallPlan) (toolOutcome, bool) {
80 if a == nil || plan == nil || a.svc.writeScheduler == nil || plan.runTool == nil {
81 return toolOutcome{}, false
82 }
83 id := SubagentClaimID(ctx)
84 if id == 0 {
85 return toolOutcome{}, false
86 }
87 name := plan.runTool.Name()
88 if plan.hooksMayMutateWorkspace {
89 if err := a.svc.writeScheduler.MarkOpaque(id); err != nil {
90 return writeClaimBlockedOutcome(err), true
91 }
92 return toolOutcome{}, false
93 }
94 if !plan.effects.WorkspaceMutation {
95 return toolOutcome{}, false
96 }
97 if pathBoundWriterNames[name] {
98 claim, err := parentWriteReservation(a.writeWorkspaceRoot, name, plan.runArgs)
99 if err != nil {
100 return writeClaimBlockedOutcome(err), true
101 }
102 if err := a.svc.writeScheduler.Realize(id, claim); err != nil {
103 return writeClaimBlockedOutcome(err), true
104 }
105 return toolOutcome{}, false
106 }
107 if parentWriteGuardTarget(name) {
108 if err := a.svc.writeScheduler.MarkOpaque(id); err != nil {
109 return writeClaimBlockedOutcome(err), true
110 }
111 }
112 return toolOutcome{}, false
113 }
114
115 func writeClaimBlockedOutcome(err error) toolOutcome {
116 return toolOutcome{
117 output: "blocked: " + err.Error(), blocked: true,
118 errMsg: "blocked: write path claimed by background subagent",
119 }
120 }
121
121 lines GO