返回 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, err := a.reserveCoordinatedParentWrite(plan)
33 if err != nil {
34 return writeClaimBlockedOutcome(err), true
35 }
36 plan.releaseParentWrite = release
37 return a.applyLiveWriteReservation(ctx, plan)
38 }
39
40 func (a *Agent) reserveCoordinatedParentWrite(plan *toolCallPlan) (func(), error) {
41 if plan.hooksMayMutateWorkspace &&
42 a.svc.writeScheduler != nil && a.subagentDepth == 0 {
43 claim, err := WholeWorkspaceWriteClaim(a.writeWorkspaceRoot)
44 if err != nil {
45 return func() {}, err
46 }
47 return a.svc.writeScheduler.ReserveParentWrite(claim)
48 }
49 return a.reserveParentWrite(plan.runTool, plan.runArgs, !plan.effects.WorkspaceMutation)
50 }
51
52 func (a *Agent) acquireWorkspaceLease(ctx context.Context, plan *toolCallPlan) (func(), error) {
53 noop := func() {}
54 if a == nil || a.svc.workspaceLease == nil || plan == nil || plan.runTool == nil {
55 return noop, nil
56 }
57 // Tool hooks are arbitrary user shell code, so their write surface cannot be
58 // narrowed to the concrete tool's path arguments.
59 if plan.hooksMayMutateWorkspace {
60 return a.svc.workspaceLease.HoldWrite(ctx)
61 }
62 name := plan.runTool.Name()
63 if pathBoundWriterNames[name] {
64 paths, err := extractWritePathsFromArgs(name, a.writeWorkspaceRoot, plan.runArgs)
65 if err == nil && len(paths) > 0 {
66 for i := range paths {
67 paths[i] = resolveMaybeRelative(a.writeWorkspaceRoot, paths[i])
68 }
69 return a.svc.workspaceLease.HoldWriteForPaths(ctx, paths)
70 }
71 }
72 return a.svc.workspaceLease.HoldWrite(ctx)
73 }
74
75 func (a *Agent) applyLiveWriteReservation(ctx context.Context, plan *toolCallPlan) (toolOutcome, bool) {
76 if a == nil || plan == nil || a.svc.writeScheduler == nil || plan.runTool == nil {
77 return toolOutcome{}, false
78 }
79 id := SubagentClaimID(ctx)
80 if id == 0 {
81 return toolOutcome{}, false
82 }
83 name := plan.runTool.Name()
84 if plan.hooksMayMutateWorkspace {
85 if err := a.svc.writeScheduler.MarkOpaque(id); err != nil {
86 return writeClaimBlockedOutcome(err), true
87 }
88 return toolOutcome{}, false
89 }
90 if !plan.effects.WorkspaceMutation {
91 return toolOutcome{}, false
92 }
93 if pathBoundWriterNames[name] {
94 claim, err := parentWriteReservation(a.writeWorkspaceRoot, name, plan.runArgs)
95 if err != nil {
96 return writeClaimBlockedOutcome(err), true
97 }
98 if err := a.svc.writeScheduler.Realize(id, claim); err != nil {
99 return writeClaimBlockedOutcome(err), true
100 }
101 return toolOutcome{}, false
102 }
103 if parentWriteGuardTarget(name) {
104 if err := a.svc.writeScheduler.MarkOpaque(id); err != nil {
105 return writeClaimBlockedOutcome(err), true
106 }
107 }
108 return toolOutcome{}, false
109 }
110
111 func writeClaimBlockedOutcome(err error) toolOutcome {
112 return toolOutcome{
113 output: "blocked: " + err.Error(), blocked: true,
114 errMsg: "blocked: write path claimed by background subagent",
115 }
116 }
117
117 lines GO